Data exploration
This project seeks to create a predictive model for gentrification
that can be used in planning practice to identify areas at risk for
gentrification or identify areas undergoing gentrification. This model
uses factors that the team at Marie Antoinette Predictions have
researched and believe are reliable markers for gentrification. The
variables in our model are construction permits, vacant properties,
affordable housing locations, green spaces, and demolitions. The data
was collected from Philadelphia’s open data portal7. After
downloading the relevant data, it was cleaned to ensure that variables
were clear and transferable.
mapTheme <- function(base_size = 12) {
theme(
text = element_text( color = "black"),
plot.title = element_text(size = 16,colour = "black"),
plot.caption=element_text(hjust=0),
axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_blank(),
axis.text = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_rect(colour = "black", fill=NA, size=2),
strip.text.x = element_text(size = 14))
}
plotTheme <- function(base_size = 12) {
theme(
text = element_text( color = "black"),
plot.title = element_text(color = "darkgreen", size=15, face="bold"),
plot.subtitle = element_text(face="italic"),
plot.caption = element_text(hjust=0),
axis.ticks = element_blank(),
panel.background = element_blank(),
panel.grid.major = element_line("grey80", size = 0.1),
panel.grid.minor = element_blank(),
panel.border = element_rect(colour = "black", fill=NA, size=2),
strip.background = element_rect(fill = "grey80", color = "white"),
strip.text = element_text(size=12),
axis.title = element_text(size=12),
axis.text = element_text(size=10),
plot.background = element_blank(),
legend.background = element_blank(),
legend.title = element_text(colour = "black", face = "italic"),
legend.text = element_text(colour = "black", face = "italic"),
strip.text.x = element_text(size = 14)
)
}
Read in Data from Philadelphia
# permits
phlPermits <-
st_read("https://phl.carto.com/api/v2/sql?q=SELECT+*+FROM+permits+WHERE+permitissuedate+>=+'2013-01-01'+AND+permitissuedate+<+'2022-12-31'&filename=permits&format=geojson&skipfields=cartodb_id") %>%
st_transform('ESRI:102728')%>%
mutate(Legend = "Philadelphia Permits")%>%
mutate(Year = as.integer(format(permitissuedate, "%Y")))
## 2. Philadelphia Boundaries
phlBoundary <-
st_read("http://data.phl.opendata.arcgis.com/datasets/405ec3da942d4e20869d4e1449a2be48_0.geojson") %>%
st_transform('ESRI:102728')
#dunno if we need to fishnet this
phlFishnet <-
st_make_grid(phlBoundary,
cellsize = 500,
square = TRUE) %>%
.[phlBoundary] %>%
st_sf() %>%
mutate(uniqueID = rownames(.))
## 3. Philadelphia Neighborhoods - isn't loading
phillyNeighborhoods <-
st_read("https://raw.githubusercontent.com/opendataphilly/open-geo-data/master/philadelphia-neighborhoods/philadelphia-neighborhoods.geojson") %>%
st_transform('ESRI:102728') %>%
st_transform(st_crs(phlFishnet))
## 4. Vacant Property
vacantBuilding <-
st_read('https://opendata.arcgis.com/datasets/f7ed68293c5e40d58f1de9c8435c3e84_0.geojson') %>%
na.omit() %>%
st_transform('ESRI:102728') %>%
st_transform(st_crs(phlFishnet)) %>%
mutate(Legend = "Vacant Buildings")
## 5. Affordable Housing
affordableHousing <-
st_read('https://opendata.arcgis.com/datasets/ca8944190b604b2aae7eff17c8dd9ef5_0.geojson') %>%
filter(FISCAL_YEAR_COMPLETE >= "2012") %>%
st_transform('ESRI:102728') %>%
st_transform(st_crs(phlFishnet)) %>%
mutate(Legend = "Affordable Housing")
## 7. Green Spaces
greenSpace <-
st_read('https://opendata.arcgis.com/datasets/d52445160ab14380a673e5849203eb64_0.geojson') %>%
st_transform('ESRI:102728') %>%
st_transform(st_crs(phlFishnet)) %>%
mutate(Legend = "Green Spaces")
## 10. Demolition Data
buildingDemolition <-
st_read('https://phl.carto.com/api/v2/sql?q=SELECT+*+FROM+demolitions&filename=demolitions&format=geojson&skipfields=cartodb_id') %>%
mutate(year = substr(start_date,1,4)) %>%
filter(year == '2022') %>%
st_transform('ESRI:102728') %>%
st_transform(st_crs(phlFishnet)) %>%
mutate(Legend = "Building Demolition")
## 11. Census data - ACS 2021
tracts22 <- get_acs(
geography = "tract",
variables = c(
"B01003_001", # Total Population
"B19013_001", # Median Household Income
"B25008_002", # Owner-Occupied Units
"B25008_003", # Renter-Occupied Units
"B25008_001E", # Total Population in Housing Units
"B15003_022", # Educational Attainment: Bachelor's Degree
"B06012_002E", # Population Below the Poverty Level
"B02001_002", # Race and Ethnicity: White Alone
"B02001_003", # Race and Ethnicity: Black or African American Alone
"B27011_008E" # Population Unemployed
),
year = 2022,
state = "PA",
county = "Philadelphia",
geometry = TRUE,
output = "wide"
)%>%
dplyr::select(-NAME, -ends_with("M")) %>%
rename(totalPop = B01003_001E, # Total Population
medHHInc = B19013_001E, # Median Household Income
totalUnit = B25008_001E, # Total Population in Housing Units
ownerOccupied = B25008_002E, # Owner-Occupied Units
renterOccupied = B25008_003E, # Renter-Occupied Units
bachDegree = B15003_022E, # Educational Attainment: Bachelor's Degree
totalPov = B06012_002E, # Population Below the Poverty Level
totalUnemploy = B27011_008E, # Population Unemployed
whiteAlone = B02001_002E, # Race and Ethnicity: White Alone
blackAlone = B02001_003E # Race and Ethnicity: Black or African American Alone
)
### 11.1. Transform the data to ESRI:102728 projection
tracts22 <- tracts22 %>% st_transform(st_crs(phlFishnet))
### 11.2 Create new variables
tracts22 <- tracts22 %>%
mutate(pctWhite = ifelse(totalPop > 0, whiteAlone / totalPop * 100,0),
pctBlack = ifelse(totalPop > 0, blackAlone / totalPop * 100,0),
pctPov = ifelse(totalPop > 0, totalPov / totalPop *100, 0),
pctUnemploy = ifelse(totalPop > 0, totalUnemploy / totalPop *100, 0),
pctBach = ifelse(totalPop > 0, bachDegree / totalPop *100, 0),
pctOwnerOccupied = ifelse(totalPop > 0, ownerOccupied / totalUnit *100, 0),
pctRenterOccupied = ifelse(totalPop > 0, renterOccupied / totalUnit *100, 0)
) %>%
dplyr::select(-whiteAlone, -blackAlone, -totalPov ,-totalUnemploy,-ownerOccupied, -renterOccupied, -totalUnit, -bachDegree, -GEOID) %>%
st_transform(st_crs(phlFishnet))
### 11.3 Organize into datasets - what is this whole organization part for??
tracts22.medHHInc <- tracts22 %>%
dplyr::select(medHHInc) %>%
rename(Legend = medHHInc)
tracts22.pctWhite <- tracts22 %>%
dplyr::select(pctWhite)%>%
rename(Legend = pctWhite)
tracts22.pctBlack <- tracts22 %>%
dplyr::select(pctBlack)%>%
rename(Legend = pctBlack)
tracts22.pctPov <- tracts22 %>%
dplyr::select(pctPov)%>%
rename(Legend = pctPov)
tracts22.pctUnemploy <- tracts22 %>%
dplyr::select(pctUnemploy)%>%
rename(Legend = pctUnemploy)
tracts22.pctBach <- tracts22 %>%
dplyr::select(pctBach)%>%
rename(Legend = pctBach)
tracts22.pctOwnerOccupied <- tracts22 %>%
dplyr::select(pctOwnerOccupied)%>%
rename(Legend = pctOwnerOccupied)
tracts22.pctRenterOccupied <- tracts22 %>%
dplyr::select(pctRenterOccupied)%>%
rename(Legend = pctRenterOccupied)
Data Visualisation
The data was visualized using mapping tools. The area from around
Center City to lower North Philadelphia and Kensington has the densest
amount of construction permits and demolition permits. There is a high
density of vacant buildings in West and North Philadelphia.
#Categorizing the permits for construction and demolition
phlPermits <- phlPermits %>%
mutate(newType = case_when(permittype == "BUILDING" | permittype == "BP_NEWCNST" ~ 'CONSTRUCTION PERMIT',
permittype == "DEMOLITION" | permittype == "BP_DEMO" ~ 'DEMOLITION PERMIT'))
cnstPermits <- phlPermits %>%
filter(newType == 'CONSTRUCTION PERMIT')
demoPermits <- phlPermits %>%
filter(newType == 'DEMOLITION PERMIT')
cnstPermits_2022 <- cnstPermits%>%
filter(Year==2022)
cnstPermits_2013 <- cnstPermits%>%
filter(Year == 2013)
The plots below show how the different types of permits are located
all over Philadelphia, looking primarily at the the permits that fall
under construction or zoning. We also look at how other various chosen
demographic and socio-economic factors are mapped out across the city to
compare them against where there is the highest density of permits.
Construction permits can be seen in higher concentrations in the areas
where there is a higher percentage of individuals with bachelor’s
degrees and higher unemployment.
# Plot 1: map of all construction and demo permits issued b/w 2013 and 2022
ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
geom_sf(data = phlPermits, aes(colour = newType), size = 0.5, show.legend = "point") +
scale_color_manual(values = c("magenta", "lightgreen")) +
labs(title = "Major Permits Issued, 2013-22 in Philadelphia",
caption = "Figure 1") +
theme_void()

# Plot 2 + 3: Mapped points and Density map of construction permits issued
ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
geom_sf(data = cnstPermits, aes(color = "magenta"), size = 0.5) +
scale_color_identity(guide = "legend", name = NULL, labels = "Construction Permits") +
labs(title = "Construction Permits Issued, 2013-22 in Philadelphia",
caption = "Figure 2") +
theme_void()

ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
stat_density2d(data = data.frame(st_coordinates(cnstPermits)),
aes(X, Y, fill = ..level.., alpha = ..level..),
size = 0.01, bins = 40, geom = 'polygon') +
scale_fill_viridis_c(option ="rocket") +
labs(title = "Density of Construction Permits") +
theme_void() + theme(legend.position = "none")

#Plot 4 + 5: Mapped points and Density map of demolition permits issued
ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
geom_sf(data = demoPermits, aes(colour = "lightgreen"), size = 0.5) +
scale_color_identity(guide = "legend", name = NULL, labels = "Demolition Permits") +
labs(title = "Demolition Permits Issued, 2013-22 in Philadelphia",
caption = "Figure 4") +
theme_void()

ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
stat_density2d(data = data.frame(st_coordinates(demoPermits)),
aes(X, Y, fill = ..level.., alpha = ..level..),
size = 0.01, bins = 40, geom = 'polygon') +
scale_fill_viridis_c(option = "rocket") +
scale_alpha(range = c(0.00, 0.35), guide = FALSE) +
labs(title = "Density of Construction Permits") +
theme_void() + theme(legend.position = "none")

# plot 6: affordable housing
ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
geom_sf(data = affordableHousing, aes(color = "orange"), size = 0.5) +
scale_color_identity(guide = "legend", name = NULL, labels = "Affordable Housing Units") +
labs(title = "Affordable Housing Developments, 2013-22 in Philadelphia",
caption = "Figure 6") +
theme_void()

#plot7:
ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
geom_sf(data = greenSpace, aes(color = "darkgreen"), size = 0.5) +
scale_color_identity(guide = "legend", name = NULL, labels = "Green Spaces") +
labs(title = "Green Spaces, 2013-22 in Philadelphia",
caption = "Figure 7") +
theme_void()

#plot8 and 9: vacant land point and density maps
ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
geom_sf(data = vacantBuilding, aes(color = 'darkred'), size = 0.5, show.legend = "point") +
scale_color_identity(guide = "legend", name = NULL, labels = "Vacant Buildings") +
labs(title = "Suspected Vacant Buildings in Philadelphia",
caption = "Figure 8") +
theme_void()

ggplot() +
geom_sf(data = phlBoundary, fill = "grey89", color = "darkgrey") +
stat_density2d(data = data.frame(st_coordinates(vacantBuilding)),
aes(X, Y, fill = ..level.., alpha = ..level..),
size = 0.01, bins = 40, geom = 'polygon') +
scale_fill_viridis_c(option = "rocket") +
scale_alpha(range = c(0.00, 0.35), guide = FALSE) +
labs(title = "Density of Vacant Buldings") +
theme_void() + theme(legend.position = "none")

#plot10: demographic
ggplot()+
geom_sf(data=tracts22, aes(color=NA, fill=pctUnemploy))+
scale_color_viridis()+
scale_fill_viridis_c()+
geom_sf(data = cnstPermits, aes(color="yellow") ,
show.legend = "point", size = .1, alpha=0.3) +
scale_color_identity() +
labs(title="% Unemployment around Permits Issued")+
mapTheme()+theme(plot.title = element_text(size = 10), legend.title=element_blank())

ggplot()+
geom_sf(data=tracts22, aes(color=NA, fill=pctBach))+
scale_color_viridis()+
scale_fill_viridis()+
geom_sf(data = cnstPermits, aes(color="yellow") ,
show.legend = "point", size = .1, alpha=0.3) +
scale_color_identity() +
labs(title="% Population with Bachelor's Degree around Permits Issued")
### Fishnet
Here we construct a geospatial dataset to examine how the risk of new
development is spread throughout Philadelphia. To do this we created a
fishnet which is a continous grid pattern consisting of cells measuring
500x500 feet. This grid allows us to conceptualize development risk,
represented by the presence of new construction permits. The following
plot depicts the count of new construction permits across the fishnet,
with grid cells depicted in yellow indicating the highest permit
counts.
# Attaching datasets on spatial factors to Fishnet
## 1. Extracting geometry for spatial factors
affordableHousings <- affordableHousing %>%
dplyr::select(geometry, Legend)
vacantBuildings <- vacantBuilding %>%
dplyr::select(geometry, Legend)
greenSpaces <- greenSpace %>%
dplyr::select(geometry, Legend)
buildingDemolitions <- buildingDemolition %>%
dplyr::select(geometry, Legend)
## 2. Creating fishnet of spatial factor variables
vars_net <-
rbind(affordableHousings, vacantBuildings,
greenSpaces, buildingDemolitions) %>%
st_join(., phlFishnet, join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID, Legend) %>%
summarize(count = n()) %>%
full_join(phlFishnet, by = "uniqueID") %>%
spread(Legend, count, fill=0) %>%
st_sf() %>%
na.omit() %>%
dplyr::select(-`<NA>`) %>%
ungroup()
cnstPermits <- st_transform(cnstPermits, st_crs(phlFishnet))
construction_net <-
dplyr::select(cnstPermits) %>%
mutate(countPermits = 1) %>%
aggregate(., phlFishnet, sum) %>%
mutate(countPermits = replace_na(countPermits, 0),
uniqueID = rownames(.),
cvID = sample(round(nrow(phlFishnet) / 24),
size=nrow(phlFishnet), replace = TRUE))
# visualising permits joined to fishnet
ggplot() +
geom_sf(data = construction_net, aes(fill = countPermits), color = NA) +
geom_sf(data = st_boundary(phlFishnet), color = "darkred", lwd = .04, alpha=.2) + # Add boundaries in red
scale_fill_viridis_c(option = "plasma",
name = 'Construction Counts') +
labs(title = "Construction Permits Joined to Fishnet",
subtitle = 'Philadelphia') + mapTheme() + plotTheme()
### Nearest Neighbour
This code segment creates a nearest neighbor feature for different
categories, including vacant buildings, affordable housing, green
spaces, and building demolitions. For each category, it first maps the
nearest feature from a given dataset to another dataset, converts the
datasets to rsgeo geometries, calculates the Euclidean
distance between each observation in one dataset and its nearest
neighbor in the other dataset. Finally, the calculated distances are
appended as new variables to the original dataset for each category,
such as dist_vacantBuilding,
dist_affordableHousing, dist_greenSpace, and
dist_buildingDemolition. Essentially this process turns
each individual grid cell into a centroid point and then measures the
distances between each to find the nearest risk factor point.
This spatial analysis is crucial for understanding the relationships
between different urban features and their proximity to one another. By
calculating the distances between variables like vacant buildings,
affordable housing, green spaces, and building demolitions, it provides
insights into the spatial distribution and potential interactions among
these features.
## 1.2. Vacant Buildings
### Mapping nearest feature
nearest_vacantBuilding <- sf::st_nearest_feature(vars_net, vacantBuilding)
### Converting to rsgeo geometries
x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(vacantBuilding)
### Calculating distance
vars_net$dist_vacantBuilding <- rsgeo::distance_euclidean_pairwise(x, y[nearest_vacantBuilding])
## 1.3. Affordable Housing
### Mapping nearest feature
nearest_affordableHousing <- sf::st_nearest_feature(vars_net, affordableHousing)
### Converting to rsgeo geometries
x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(affordableHousing)
### Calculating distance
vars_net$dist_affordableHousing <- rsgeo::distance_euclidean_pairwise(x, y[nearest_affordableHousing])
## 1.4. Green Spaces
### Mapping nearest feature
nearest_greenSpace <- sf::st_nearest_feature(vars_net, greenSpace)
### Converting to rsgeo geometries
x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(greenSpace)
### Calculating distance
vars_net$dist_greenSpace <- rsgeo::distance_euclidean_pairwise(x, y[nearest_greenSpace])
## 1.8. Building Demolitions
### Mapping nearest feature
nearest_buildingDemolition <- sf::st_nearest_feature(vars_net, buildingDemolition)
### Converting to rsgeo geometries
x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(buildingDemolition)
### Calculating distance
vars_net$dist_buildingDemolition <- rsgeo::distance_euclidean_pairwise(x, y[nearest_buildingDemolition])
# 2. Visualizing nearest distance for spatial factors on Fishnet
## 2.1. Visualizing the nearest three features
vars_net.long.nn <-
dplyr::select(vars_net, starts_with("dist")) %>%
gather(Variable, value, -geometry)
vars <- unique(vars_net.long.nn$Variable)
mapList <- list()
for(i in vars){
mapList[[i]] <-
ggplot() +
geom_sf(data = filter(vars_net.long.nn, Variable == i), aes(fill=value), colour=NA) +
scale_fill_viridis_c(option = "plasma",
name = " ") +
labs(title=i) +
mapTheme()+
theme(plot.title = element_text(size = 12, color = "black"))
}
bottomCaption <- textGrob("Figure 8", gp = gpar(hjust = 0))
do.call(grid.arrange, c(list(grobs = mapList, ncol = 2,
top = textGrob("Spatial Factors: Nearest Neighbor Distance for Permits Issued\n",
gp = gpar(fontsize = 15, fontface = "bold", col = "darkred")),
bottom = bottomCaption)))

Spatial Joins
Here we want to join the census data to our fishnet so we can also
map our demographic variables in a similar fashion, and also just
standardize our mode of analysis across all our data.
# Joining Census Data to Fishnet
tracts22 <- tracts22 %>%
filter(totalPop>0)
vars_net <-
vars_net%>%
st_centroid()%>%
st_join(tracts22)
vars_net <- vars_net %>% mutate_all(~replace(., is.na(.), 0))
# Perform Spatial Join of variables with permits
final_net <-
left_join(construction_net, st_drop_geometry(vars_net), by="uniqueID") # this one doesn't work so the last one won't either
# Final Net
final_net <-
st_centroid(final_net) %>%
st_join(dplyr::select(phillyNeighborhoods, name), by = "uniqueID") %>%
st_drop_geometry() %>%
left_join(dplyr::select(final_net, geometry, uniqueID)) %>%
st_sf() %>%
na.omit()
## generates warnings from PROJ issues
## {spdep} to make polygon to neighborhoods...
final_net.nb <- poly2nb(as_Spatial(final_net), queen=TRUE)
## ... and neighborhoods to list of weigths
final_net.weights <- nb2listw(final_net.nb, style="W", zero.policy=TRUE)
# print(final_net.weights, zero.policy=TRUE)
Building and evaluating the model
The model used in this analysis is a poisson regression model. The
collected and cleaned data that was used in this model was demographic
information like race, income, and work status; housing information like
percent renter and owner occupied; regional characteristics like vacant
buildings, construction permits, affordable housing, and
demolitions.
Correlation
correlation.long <-
st_drop_geometry(final_net) %>%
dplyr::select(-uniqueID, -cvID, -name) %>%
gather(Variable, Value, -countPermits) %>%
mutate(Value = as.numeric(Value))
correlation.cor <-
correlation.long %>%
group_by(Variable) %>%
summarize(correlation = cor(Value, countPermits, use = "complete.obs"))
# Visualizing correlations through scatter plots
#we have a lot of demographic variables here that i don't know if we necessarily need or are interested in keeping for our final stuff - think it may be better to pick and choose fewer demo variables and maybe choose more external variables
ggplot(correlation.long, aes(Value, countPermits)) +
geom_point(size = 0.1) +
geom_text(data = correlation.cor, aes(label = paste("r =", round(correlation, 2))),
x=-Inf, y=Inf, vjust = 1.5, hjust = -.1) +
geom_smooth(method = "lm", se = FALSE, colour = "orange") +
facet_wrap(~Variable, ncol = 4, scales = "free") +
labs(title = "Permit Count as a function of risk factors", caption="Figure 12") +
plotTheme()

numvars <- c("countPermits", "dist_vacantBuilding", "dist_affordableHousing","dist_greenSpace", "dist_buildingDemolition", "totalPop", "medHHInc", "pctWhite", "pctBlack", "pctBach" ,"pctPov", "pctUnemploy", "pctOwnerOccupied", "pctRenterOccupied")
numeric <- final_net %>%
st_drop_geometry(final_net) %>%
dplyr::select(numvars)%>%
na.omit()
ggcorrplot(
round(cor(numeric), 1),
p.mat = cor_pmat(numeric),
colors = c('#d7191c','white','#2c7bb6'),
type="lower",
insig = "blank") +
labs(title = "Correlation across Variables\n", caption="Figure 13")+
theme(plot.title = element_text(size = 11, face = "bold", color = "darkred"))+
theme(axis.text.x=element_text(size=8))+
theme(axis.text.y=element_text(size=8))
There seems to be relatively high positive correlation between median
household income and the percent of the population that is white. This
may suggest that areas with more white residents are higher earning
neighborhoods, or a neighborhood that is gentrifying may have more
higher income and more white residents. There is a relatively high
negative correlation between total population and the amount of vacant
buildings. This may mean that there are less vacant buildings in more
populous areas - this could relate to gentrification in the way that
more crowded, coveted neighborhoods will be more pressed for space,
leading to redevelopment of vacant properties.
Conducting a regression model in this context is crucial for several
reasons. Firstly, it allows for the quantification of relationships
between various factors and the outcome of interest, such as the number
of permits issued or the likelihood of development. This enables the
identification of significant predictors and their respective impact on
the outcome, aiding in understanding the underlying dynamics of
development risk. Additionally, regression modeling provides a means to
assess the relative importance of different variables, prioritize
interventions, and inform decision-making processes aimed at mitigating
development-related challenges or promoting sustainable urban
growth.
Cross-validation is performed to evaluate the predictive performance
of Poisson regression models applied to the dataset. This process
involves splitting the dataset into distinct folds, where each fold
serves as a training set for model development and a testing set for
performance assessment. Within each iteration, a Poisson regression
model is trained using the training data and then applied to the testing
data to make predictions. These predictions are compared against the
actual values to assess the model’s accuracy and robustness. The
cross-validation procedure helps ensure that the models generalize well
to unseen data and provide reliable predictions in real-world scenarios,
enhancing the overall reliability of the analysis.
The provided code implements a cross-validation procedure for
evaluating Poisson regression models applied to the dataset
final_net. It defines a function
crossValidate() to conduct cross-validation, where the
dataset is split into distinct folds (cvID) for training
and testing. Within each fold, a Poisson regression model is trained
using the training data (fold.train) and then applied to
the testing data (fold.test) to make predictions. These
predictions are aggregated across all folds to assess the overall
predictive performance of the model. The cross-validation helps validate
the model’s ability to generalize to unseen data, ensuring the
reliability of the regression analysis.
Variable Selection
Based on the correlation analysis, variables such as ‘Vacant Building
Distance’, ‘Affordable Housing Distance’, ‘Green Space Distance’,
‘Building Demolition Distance’, ‘Median Household Income’, ‘Percent
Below Poverty Line’, ‘Percent Unemployed’, and ‘Percent Owner Occupied’
are selected as model variables.
## define the variables we want
reg.vars <- c("dist_vacantBuilding", "dist_affordableHousing","dist_greenSpace", "dist_buildingDemolition", "medHHInc", "pctBach" ,"pctPov", "pctUnemploy", "pctOwnerOccupied")
reg.ss.vars <- c("dist_vacantBuilding", "dist_affordableHousing","dist_greenSpace", "dist_buildingDemolition", "medHHInc", "pctBach" ,"pctPov", "pctUnemploy", "pctOwnerOccupied", "permit.isSig")
## creating functions for cross validation
crossValidate <- function(dataset, id, dependentVariable, indVariables) {
allPredictions <- data.frame()
cvID_list <- unique(dataset[[id]])
for (i in cvID_list) {
thisFold <- i
cat("This hold out fold is", thisFold, "\n")
fold.train <- filter(dataset, dataset[[id]] != thisFold) %>% as.data.frame() %>%
dplyr::select(id, geometry, indVariables, dependentVariable)
fold.test <- filter(dataset, dataset[[id]] == thisFold) %>% as.data.frame() %>%
dplyr::select(id, geometry, indVariables, dependentVariable)
regression <-
glm(countPermits ~ ., family = "poisson",
data = fold.train %>%
dplyr::select(-geometry, -id))
thisPrediction <-
mutate(fold.test, Prediction = predict(regression, fold.test, type = "response"))
allPredictions <-
rbind(allPredictions, thisPrediction)
}
return(st_sf(allPredictions))
}
Cross Validation
This shows the process of conducting cross-validation on Poisson
regression models using the crossValidate function, aimed at predicting
the number of construction permits (countPermits) in Philadelphia
neighborhoods. Different models are being evaluated: a non-spatial
process model using a defined set of variables (reg.vars) and a spatial
process model, which integrates spatial characteristics along with the
same set of variables to understand how including spatial data
influences the accuracy and predictiveness of the model outcomes. This
cross-validation approach helps in assessing the model’s robustness and
generalizability by testing it on multiple subsets of data.
#conducting cross validation on Poisson regressino models
reg.cv <- crossValidate(
dataset = final_net,
id = "cvID",
dependentVariable = "countPermits",
indVariables = reg.vars) %>%
dplyr::select(cvID = cvID, countPermits, Prediction, geometry)
reg.ss.cv <- crossValidate(
dataset = final_net,
id = "cvID",
dependentVariable = "countPermits",
indVariables = reg.ss.vars) %>%
dplyr::select(cvID = cvID, countPermits, Prediction, geometry)
final_net$name <- ifelse(is.na(final_net$name), "UNKNOWN", final_net$name)
reg.spatialCV <- crossValidate(
dataset = final_net,
id = "name",
dependentVariable = "countPermits",
indVariables = reg.vars) %>%
dplyr::select(cvID = name, countPermits, Prediction, geometry)
reg.ss.spatialCV <- crossValidate(
dataset = final_net,
id = "name",
dependentVariable = "countPermits",
indVariables = reg.ss.vars) %>%
dplyr::select(cvID = name, countPermits, Prediction, geometry)
Error Calculation
In each regression, the absolute error is determined by calculating
the difference between the predicted and the actual observed counts of
new construction permits.
# calculate errors
reg.summary <-
rbind(
mutate(reg.cv, Error = Prediction - countPermits,
Regression = "Random k-fold CV: Just Risk Factors"),
mutate(reg.ss.cv, Error = Prediction - countPermits,
Regression = "Random k-fold CV: Spatial Process"),
mutate(reg.spatialCV, Error = Prediction - countPermits,
Regression = "Spatial LOGO-CV: Just Risk Factors"),
mutate(reg.ss.spatialCV, Error = Prediction - countPermits,
Regression = "Spatial LOGO-CV: Spatial Process")) %>%
st_sf()
# Calculate MAE and standard deviation for each fold and method
error_by_reg_and_fold <-
reg.summary %>%
group_by(Regression, cvID) %>%
summarize(Mean_Error = mean(Prediction - countPermits, na.rm = T),
MAE = mean(abs(Mean_Error), na.rm = TRUE),
SD_MAE = mean(abs(Mean_Error), na.rm = TRUE),
.groups = 'drop'
)
# Arrange by MAE for viewing
error_by_reg_and_fold %>%
arrange(desc(MAE))
error_by_reg_and_fold %>%
arrange(MAE)
# Plot histogram of OOF errors for each method
error_by_reg_and_fold %>%
ggplot(aes(x = MAE)) +
geom_histogram(bins = 30, colour="black", fill = "#FDE725FF") +
facet_wrap(~ Regression, scales = "free") +
scale_x_continuous(breaks = seq(0, 11, by = 1)) +
labs(title="Distribution of MAE", subtitle = "Random K-Fold and LOGO-CV",
x="Mean Absolute Error", y="Count") +
theme(plot.title = element_text(size = 15, face= "bold", color = "darkred"))

error_by_reg_and_fold %>%
filter(str_detect(Regression, "LOGO")) %>%
ggplot() +
geom_sf(data = phlBoundary, fill = "white", color = "darkgrey") +
geom_sf(aes(fill = MAE)) +
facet_wrap(~Regression) +
scale_colour_viridis(option = "plasma") +
scale_fill_viridis(option = "plasma") +
labs(title = "Errors by LOGO-CV Regression", caption="Figure 15") +
theme(plot.title = element_text(size = 15, face= "bold", color = "darkred"))+
theme(strip.text = element_text(size = 8))

# Table of MAE and Standard Deviation MAE
st_drop_geometry(error_by_reg_and_fold) %>%
group_by(Regression) %>%
summarize(Mean_MAE = round(mean(MAE), 2),
SD_MAE = round(sd(MAE), 2)) %>%
kable(caption = "Table 1: MAE and standard deviation MAE by regression") %>%
kable_styling("striped", full_width = F) %>%
row_spec(2, color = "black", background = "#FDE725FF") %>%
row_spec(4, color = "black", background = "#FDE725FF")
Table 1: MAE and standard deviation MAE by regression
|
Regression
|
Mean_MAE
|
SD_MAE
|
|
Random k-fold CV: Just Risk Factors
|
0.58
|
0.58
|
|
Random k-fold CV: Spatial Process
|
0.54
|
0.53
|
|
Spatial LOGO-CV: Just Risk Factors
|
1.39
|
2.71
|
|
Spatial LOGO-CV: Spatial Process
|
0.98
|
1.83
|
The areas with some of the greatest mean absolute error are closer to
center city, such as Fishtown, University City, and Graduate Hospital.
Regions of Philadelphia close to the North, West, South, and along the
Delaware river showed lower errors, both in terms of just risk factors
and spatial process. There was higher error shown in the Spatial LOGO-CV
process than the other validation methods. The random k-fold processes
resulted in lower mean absolute error.
# Assuming 'error_by_reg_and_fold' contains the necessary columns and spatial data
# First, ensure that your data frame has the appropriate structure and unique row names
# Check for unique row names and reset if necessary
if(anyDuplicated(row.names(error_by_reg_and_fold))) {
rownames(error_by_reg_and_fold) <- make.unique(as.character(row.names(error_by_reg_and_fold)))
}
# Create weights only for the selected regression type
neighborhood.weights <- error_by_reg_and_fold %>%
filter(Regression == "Spatial LOGO-CV: Spatial Process") %>%
st_as_sf() %>%
group_by(cvID) %>%
poly2nb(., queen = TRUE) %>% # Corrected: Removed style and zero.policy
nb2listw(., style = "W", zero.policy = TRUE) # Correct usage of style and zero.policy
filter(error_by_reg_and_fold, str_detect(Regression, "LOGO")) %>%
st_drop_geometry() %>%
group_by(Regression) %>%
summarize(Morans_I = moran.mc(abs(Mean_Error), neighborhood.weights,
nsim = 999, zero.policy = TRUE,
na.action=na.omit)[[1]],
p_value = moran.mc(abs(Mean_Error), neighborhood.weights,
nsim = 999, zero.policy = TRUE,
na.action=na.omit)[[3]]) %>%
kable(caption = "Table 2: Moran's I on Errors by Regression") %>%
kable_styling("striped", full_width = F) %>%
row_spec(1, color = "black", background = "#FDE725FF") %>%
row_spec(1, color = "black", background = "#FDE725FF")
This displays the residuals of different regression models used to
predict new construction permits, segmented by model type. From the
plot, it’s evident that the distribution of residuals varies across the
models, with some showing a tighter cluster around the zero line
(indicating better prediction accuracy) and others displaying more
spread, suggesting less precision.
reg.summary <- reg.summary %>%
mutate(Residuals = countPermits - Prediction)
# residual plot
ggplot(reg.summary, aes(x = Prediction, y = Residuals)) +
geom_point(alpha = 0.4) + # Use alpha to adjust point transparency
geom_hline(yintercept = 0, linetype = "dashed", color = "red") + # Add a horizontal line at y = 0
labs(title = "Residual Plot", x = "Predicted Values", y = "Residuals") +
theme_minimal()

ggplot(reg.summary, aes(x = Prediction, y = Residuals, color = Regression)) +
geom_point(alpha = 0.4) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
facet_wrap(~ Regression) + # Separate plot for each regression type
labs(title = "Residual Plot by Regression Type", x = "Predicted Values", y = "Residuals") +
scale_color_viridis_d() + # Corrected to use discrete color scale
theme_minimal()

Kernel Density
The series of plots provided illustrate the spatial distribution of
construction permits in Philadelphia using kernel density estimates with
different search radii and a comparison of these estimates with spatial
risk predictions. The first set of images shows kernel density maps for
construction permits using three different search radii (1000 ft., 1500
ft., and 2000 ft.). As the search radius increases, the density map
becomes smoother and broader, indicating a generalization in the
concentration of construction activity across the city. This helps
identify which areas are experiencing high volumes of construction
relative to others, potentially pointing to hotspots of development or
gentrification.
# demo of kernel width
permits_ppp <- as.ppp(st_coordinates(cnstPermits), W = st_bbox(final_net))
permits_KD.1000 <- density.ppp(permits_ppp, 1000)
permits_KD.1500 <- density.ppp(permits_ppp, 1500)
permits_KD.2000 <-density.ppp(permits_ppp, 2000)
permits_KD.df <- rbind(
mutate(data.frame(rasterToPoints(mask(raster(permits_KD.1000), as(phillyNeighborhoods, 'Spatial')))), Legend = "1000 Ft."),
mutate(data.frame(rasterToPoints(mask(raster(permits_KD.1500), as(phillyNeighborhoods, 'Spatial')))), Legend = "1500 Ft."),
mutate(data.frame(rasterToPoints(mask(raster(permits_KD.2000), as(phillyNeighborhoods, 'Spatial')))), Legend = "2000 Ft."))
permits_KD.df$Legend <- factor(permits_KD.df$Legend, levels = c("1000 Ft.", "1500 Ft.", "2000 Ft."))
ggplot(data=permits_KD.df, aes(x=x, y=y)) +
geom_raster(aes(fill=layer)) +
facet_wrap(~Legend) +
coord_sf(crs=st_crs(final_net)) +
scale_fill_viridis(name="Density") +
labs(title = "Kernel density with 3 different search radii") +
theme_void()

#works but i dont think is needed unless there's one specific tihng we wanna look at
as.data.frame(permits_KD.1000) %>%
st_as_sf(coords = c("x", "y"), crs = st_crs(final_net)) %>%
aggregate(., final_net, mean) %>%
ggplot() +
geom_sf(aes(fill=value)) +
geom_sf(data = sample_n(cnstPermits, 1500), size = .5) +
scale_fill_viridis(name = "Density") +
labs(title = "Kernel density of Construction Permits") +
theme_void()
### Comparing models
The plots below delve deeper by overlaying actual permit locations on
the density estimates and comparing these against spatial risk
predictions. These risk predictions categorize areas based on the
predicted level of construction activity (from “Minimal” to “Very High”
risk), allowing for a nuanced understanding of development intensity.
These visualizations are crucial as they highlight areas where
infrastructure and regulations might need to be reassessed due to rapid
development. Specifically, areas classified as “Very High Risk” may
require more attention to manage the impacts of gentrification, such as
displacement and changes in the socio-economic fabric.
# Prediction by Kernel Density Model
permits_KD_sf_2022 <- as.data.frame(permits_KD.1500) %>%
st_as_sf(coords = c("x", "y"), crs = st_crs(final_net)) %>%
aggregate(., final_net, mean) %>%
mutate(label = "Kernel Density Estimates",
Risk_Level = ntile(value, 5), # Change granularity of risk levels
Risk_Level = case_when(
Risk_Level == 5 ~ "Very High Risk",
Risk_Level == 4 ~ "High Risk",
Risk_Level == 3 ~ "Moderate Risk",
Risk_Level == 2 ~ "Low Risk",
TRUE ~ "Minimal Risk")) %>%
cbind(
aggregate(
dplyr::select(cnstPermits_2022) %>% mutate(permitCount = 1), ., sum) %>%
mutate(permitCount = replace_na(permitCount, 0))) %>%
dplyr::select(label, Risk_Level, permitCount)
# Prediction by Risk Prediction Model
permits_KD_risk_sf_2022 <-
reg.ss.spatialCV %>%
mutate(label = "Spatial Risk Predictions",
Risk_Level = ntile(Prediction, 5),
Risk_Level = case_when(
Risk_Level == 5 ~ "Very High Risk",
Risk_Level == 4 ~ "High Risk",
Risk_Level == 3 ~ "Moderate Risk",
Risk_Level == 2 ~ "Low Risk",
TRUE ~ "Minimal Risk")) %>%
cbind(
aggregate(
dplyr::select(cnstPermits_2022) %>% mutate(permitCount = 1), ., sum) %>%
mutate(permitCount = replace_na(permitCount, 0))) %>%
dplyr::select(label, Risk_Level, permitCount)
unique(permits_KD_sf_2022$Risk_Level) # Check for the 'permits_KD_sf' dataset
unique(permits_KD_risk_sf_2022$Risk_Level) # Check for the 'permits_KD_risk_sf' dataset
summary(permits_KD_sf_2022$Risk_Level)
summary(permits_KD_risk_sf_2022$Risk_Level)
# Ensure 'option' and 'direction' are set to handle fewer categories
scale_fill_viridis_d(option = "plasma", direction = 1, begin = 0, end = 1, name = "Risk Level")
# Plotting comparison between both models
rbind(permits_KD_sf_2022, permits_KD_risk_sf_2022) %>%
na.omit() %>%
gather(Variable, Value, -label, -Risk_Level, -geometry) %>%
ggplot() +
geom_sf(aes(fill = Risk_Level), colour = NA) +
geom_sf(data = sample_n(cnstPermits_2022, 1130), size = .5, colour = "black") +
facet_wrap(~label, ) +
scale_fill_viridis_d(option = "plasma",
name = 'Risk Level') +
labs(title="Comparison of Kernel Density and Risk Predictions",
subtitle="Bottom Layer is 2022 Predicted Permit Counts.\nDots are observed 2022 Permit Counts.\n",
caption = 'Figure 19') +
mapTheme() +
plotTheme()

The chart below reveals significant differences in the distribution
of predicted permit counts between the two models. Spatial Risk
Predictions consistently estimate higher rates of permits across all
risk categories when compared to Kernel Density Estimates, with
particularly stark contrasts in the “Very High Risk” and “Low Risk”
categories. This suggests that the Spatial Risk Prediction model may be
more sensitive to factors that predict higher construction activity,
potentially providing a more dynamic or nuanced insight into the spatial
distribution of construction risk compared to the more generalized
Kernel Density approach.
# Comparison of predictions - Bar Plots
rbind(permits_KD_sf_2022, permits_KD_risk_sf_2022) %>%
st_set_geometry(NULL) %>%
na.omit() %>%
gather(Variable, Value, -label, -Risk_Level) %>%
group_by(label, Risk_Level) %>%
summarize(permitCount = sum(Value)) %>%
ungroup() %>%
group_by(label) %>%
mutate(Rate_of_permit_count = permitCount / sum(permitCount)) %>%
ggplot(aes(Risk_Level,Rate_of_permit_count)) +
geom_bar(aes(fill=label), position="dodge", stat="identity") +
scale_fill_viridis_d(option = "plasma",
name = 'Model') +
labs(title = "Risk Prediction vs. Kernel Density",
subtitle = 'Philadelphia, PA',
caption = 'Figure 20',
x = 'Risk Level',
y = 'Rate of Permit Counts') +
plotTheme()

Risk Mapping
Neighborhoods that may be at a high risk of gentrification by the
proxy of new construction permits are mostly located near center city.
Most of West Philadelphia, a large portion of North Philadelphia, and
some parts of the Northwest are at risk of gentrification. The highest
risk neighborhoods are Point Breeze, Fishtown, Rittenhouse, and Northern
Liberties.
In addition to these areas, large swathes of West Philadelphia and
significant portions of North Philadelphia, as well as some parts of the
Northwest, are depicted as moderate to high risk. This extensive
distribution suggests a broad impact of development that may influence
housing markets, demographic shifts, and local economies across a
considerable part of the city.
# Assigning Risk Categories for Predictions
permit_risk_sf_2022 <-
filter(reg.summary, Regression == "Spatial LOGO-CV: Spatial Process") %>%
mutate(label = "Spatial Risk Predictions",
Risk_Level = ntile(Prediction, 5),
Risk_Level = case_when(
Risk_Level == 5 ~ "Very High Risk",
Risk_Level == 4 ~ "High Risk",
Risk_Level == 3 ~ "Moderate Risk",
Risk_Level == 2 ~ "Low Risk",
TRUE ~ "Minimal Risk"))
permit_risk_sf_2022 %>%
gather(Variable, Value, -label, -Risk_Level, -geometry) %>%
ggplot() +
geom_sf(aes(fill = Risk_Level), colour = NA) +
geom_sf(data=phillyNeighborhoods, fill = "transparent") +
scale_fill_viridis(discrete = TRUE) +
labs(title="Risk Predictions",
subtitle="New Construction Permit Predictions",
caption="Figure 20") +
mapTheme()+
plotTheme()

# Neighborhoods at highest risk of permitting construction
highest_risk_nhoods <-
permit_risk_sf_2022 %>%
dplyr::select(cvID, Risk_Level, countPermits) %>%
dplyr::filter(., Risk_Level == "Very High Risk") %>% # Adjusted to match the new risk category
st_drop_geometry() %>%
group_by(cvID) %>%
summarise(Predicted_Permits = sum(countPermits)) %>%
dplyr::filter(., Predicted_Permits > 52) %>%
arrange(., desc(Predicted_Permits)) %>%
rename(Neighborhood = cvID)
# Plotting the highest risk neighborhoods
ggplot(highest_risk_nhoods) +
geom_bar(aes(x = reorder(Neighborhood, -Predicted_Permits), y = Predicted_Permits), fill = "#FE9900", stat = "identity", width = 0.5) +
labs(x = "Neighborhood", y = "Predicted Permits",
title = "Neighborhoods with Very High Permitting Risk", subtitle = 'Philadelphia, PA', caption = "Figure 21") +
theme(axis.text.x = element_text(size = 6, angle = 60, vjust = 0.7, hjust = 0.7),
axis.text.y = element_text(size = 6),
axis.title = element_text(size = 8),
plot.title = element_text(size = 9, face = "bold", color = "darkred"),
plot.subtitle = element_text(size = 7, face = "italic", color = "black"),
plot.margin = margin(0, 50, 8, 50, "pt"))
The implications of such widespread development are complex, potentially
leading to increased property values and living costs, which could
displace long-standing residents and alter the cultural fabric of these
neighborhoods. The maps help identify where proactive measures might be
necessary to mitigate the adverse effects of gentrification, such as
promoting affordable housing initiatives and supporting local businesses
to preserve neighborhood character and inclusivity.
Comparing to a different time
Let’s see how our model performed relative to KD on another year’s
data. Comparing the spatial risk predictions and kernel density
estimates from 2013 to 2022 in Philadelphia, there is a noticeable shift
in risk distribution and the concentration of predicted permit counts.
In 2013, the very high-risk categories were predominantly emphasized in
the kernel density model, indicating a higher concentration of
construction activity in a few areas. By 2022, the spatial risk
predictions model shows a more distributed risk across the city, with a
significant portion of the city classified under very high risk,
suggesting an expansion in areas targeted for new construction. This
shift could imply a broadening of development focus, potentially
spreading gentrification pressures more widely across Philadelphia. This
comparison highlights the dynamic nature of urban development and the
increasing spread of high-risk areas over the years, necessitating
updated and responsive planning and policy measures.
permits_KD_sf_2013 <- as.data.frame(permits_KD.1500) %>%
st_as_sf(coords = c("x", "y"), crs = st_crs(final_net)) %>%
aggregate(., final_net, mean) %>%
mutate(label = "Kernel Density Estimates",
Risk_Level = ntile(value, 5), # Change granularity of risk levels
Risk_Level = case_when(
Risk_Level == 5 ~ "Very High Risk",
Risk_Level == 4 ~ "High Risk",
Risk_Level == 3 ~ "Moderate Risk",
Risk_Level == 2 ~ "Low Risk",
TRUE ~ "Minimal Risk")) %>%
cbind(
aggregate(
dplyr::select(cnstPermits_2013) %>% mutate(permitCount = 1), ., sum) %>%
mutate(permitCount = replace_na(permitCount, 0))) %>%
dplyr::select(label, Risk_Level, permitCount)
# Prediction by Risk Prediction Model
permits_KD_risk_sf_2013 <-
reg.ss.spatialCV %>%
mutate(label = "Spatial Risk Predictions",
Risk_Level = ntile(Prediction, 5),
Risk_Level = case_when(
Risk_Level == 5 ~ "Very High Risk",
Risk_Level == 4 ~ "High Risk",
Risk_Level == 3 ~ "Moderate Risk",
Risk_Level == 2 ~ "Low Risk",
TRUE ~ "Minimal Risk")) %>%
cbind(
aggregate(
dplyr::select(cnstPermits_2013) %>% mutate(permitCount = 1), ., sum) %>%
mutate(permitCount = replace_na(permitCount, 0))) %>%
dplyr::select(label, Risk_Level, permitCount)
unique(permits_KD_sf_2013$Risk_Level) # Check for the 'permits_KD_sf' dataset
unique(permits_KD_risk_sf_2013$Risk_Level) # Check for the 'permits_KD_risk_sf' dataset
summary(permits_KD_sf_2013$Risk_Level)
summary(permits_KD_risk_sf_2013$Risk_Level)
# Ensure 'option' and 'direction' are set to handle fewer categories
scale_fill_viridis_d(option = "plasma", direction = 1, begin = 0, end = 1, name = "Risk Level")
# Plotting comparison between both models
rbind(permits_KD_sf_2013, permits_KD_risk_sf_2013) %>%
na.omit() %>%
gather(Variable, Value, -label, -Risk_Level, -geometry) %>%
ggplot() +
geom_sf(aes(fill = Risk_Level), colour = NA) +
geom_sf(data = sample_n(cnstPermits_2013, 1130), size = .5, colour = "black") +
facet_wrap(~label, ) +
scale_fill_viridis_d(option = "plasma",
name = 'Risk Level') +
labs(title="Comparison of Kernel Density and Risk Predictions",
subtitle="Bottom Layer is 2013 Predicted Permit Counts.\nDots are observed 2013 Permit Counts.\n",
caption = 'Figure __') +
mapTheme() +
plotTheme()

rbind(permits_KD_risk_sf_2013, permits_KD_risk_sf_2022) %>%
na.omit() %>%
gather(Variable, Value, -label, -Risk_Level, -geometry) %>%
ggplot() +
geom_sf(aes(fill = Risk_Level), colour = NA) +
geom_sf(data = sample_n(cnstPermits, 3000), size = .5, colour = "black") +
facet_wrap(~label, ) +
scale_fill_viridis(discrete = TRUE) +
labs(title="Comparison of Kernel Density and Risk Predictions",
subtitle="2013 Construction Permits Density; 2022 Construction Permits Density") +
mapTheme()

rbind(permits_KD_sf_2013, permits_KD_risk_sf_2013) %>%
st_drop_geometry() %>%
na.omit() %>%
gather(Variable, Value, -label, -Risk_Level) %>%
group_by(label, Risk_Level) %>%
summarize(countPermits = sum(Value)) %>%
ungroup() %>%
group_by(label) %>%
mutate(Pcnt_cnst_permits = countPermits / sum(countPermits)) %>%
ggplot(aes(Risk_Level,Pcnt_cnst_permits)) +
geom_bar(aes(fill=label), position="dodge", stat="identity") +
scale_fill_viridis(discrete = TRUE, name = "Model") +
labs(title = "Risk prediction vs. Kernel density, 2013",
y = "% of Test Set (per model)",
x = "Risk Level") +
theme_bw() +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5))

LS0tCnRpdGxlOiAnRmluYWw6IE1lYXN1cmluZyBHZW50cmlmaWNhdGlvbicKYXV0aG9yOiAiS2FteWEgS2hhbmRlbHdhbCwgUmV2YXRoaSBNYWNoYW4sIENsYXVkaWEgU2NocmVpZXIiCmRhdGU6ICIwNS8xMy8yMDI0IgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsICAKLS0tCgoqVE86KiBBa3NoYXkgTWFsaWssIENpdHkgb2YgUGhpbGFkZWxwaGlhIFNtYXJ0IENpdGllcyBEaXJlY3RvcgoqRlJPTToqIEthbXlhIEtoYW5kZWx3YWwsIFJldmF0aGkgTWFjaGFuLCBDbGF1ZGlhIFNjaHJlaWVyCipEQVRFOiogMTMgTWF5IDIwMjQKKlJFOiogUHJlZGljdGl2ZSBtb2RlbGluZyBmb3IgZ2VudHJpZmljYXRpb24gaW4gUGhpbGFkZWxwaGlhCgojIyBHZW50cmlmaWNhdGlvbiBpbiBQaGlsYWRlbHBoaWEgCgpQaGlsYWRlbHBoaWEgaGFzIGJlZW4gZXhwZXJpZW5jaW5nIGEgY29zdC1vZi1saXZpbmcgY3Jpc2lzIGZvciBkZWNhZGVzLiBGb3IgYSBjaXR5IGNvbW1vbmx5IHJlZmVycmVkIHRvIGFzIHRoZSBuYXRpb27igJlzIHBvb3Jlc3QgbGFyZ2VzdCBjaXR5LCB0aGVyZSBpcyBtdWNoIHdvcnJ5IGFib3V0IHRoZSBmdXR1cmUgb2YgbmV3IGRldmVsb3BtZW50cyBhbmQgaG91c2luZ14xXi4gUGhpbGFkZWxwaGlhIGhhcyB0aGUgaGlnaGVzdCBwb3ZlcnR5IHJhdGUgb3V0IG9mIHRoZSB0ZW4gbGFyZ2VzdCBjaXRpZXMgaW4gdGhlIFVTLiBJbiAyMDE4LCBhcm91bmQgMjMxLDAwMCBQaGlsYWRlbHBoaWEgaG91c2Vob2xkcyB3ZXJlIGNvc3QtYnVyZGVuZWQsIHdoaWNoIGlzIGRlZmluZWQgYnkgdGhlIFVTIERlcGFydG1lbnQgb2YgSG91c2luZyBhbmQgVXJiYW4gRGV2ZWxvcG1lbnQgYXMgYSBzaXR1YXRpb24gd2hlbiBhIGhvdXNlaG9sZCBzcGVuZHMgMzAlIG9yIG1vcmUgb2YgaXRzIGluY29tZSBvbiBob3VzaW5nIGNvc3RzLCBpbmNsdWRpbmcgcmVudCwgbW9ydGdhZ2UgcGF5bWVudHMsIHV0aWxpdGllcywgaW5zdXJhbmNlLCBhbmQgcHJvcGVydHkgdGF4ZXNeMl4uIFRoaXMgcHJvYmxlbSBpcyBtb3JlIHNldmVyZSBmb3IgcmVudGVycyDigJMgYW1vbmcgcmVudGVycyB3aXRoIGluY29tZXMgYmVsb3cgJDMwLDAwMCBwZXIgeWVhciwgODglIGFyZSBjb3N0LWJ1cmRlbmVkIGFuZCA2OCUgYXJlIHNldmVyZWx5IGNvc3QtYnVyZGVuZWQgKHNwZW5kaW5nIG1vcmUgdGhhbiA1MCUgb2YgaW5jb21lIG9uIGhvdXNpbmcpLiAgCgpXaXRoIGxvdHMgb2YgY29zdC1idXJkZW5lZCBob3VzZWhvbGRlcnMsIGluY2x1ZGluZyBob21lb3duZXJzIGFuZCByZW50ZXJzLCBpbiB0aGUgY2l0eSwgaXQgc2VlbXMgdGhhdCB0aGUgZmVhciAgb2YgZGlzcGxhY2VtZW50IGFuZCBnZW50cmlmaWNhdGlvbiBpbiBtYW55IG5laWdoYm9yaG9vZHMgaXMgY29tbW9uLiBIaXN0b3JpY2FsbHkgaGlnaCByZW50cyBhbmQgbW9ydGdhZ2UgcmF0ZXMsIGNvbWJpbmVkIHdpdGggdW5wcmVjZWRlbnRlZCBsZXZlbHMgb2YgbmV3IGRldmVsb3BtZW50cyBoYXZlIHNwdXJyZWQgZGlzY3Vzc2lvbiBhcm91bmQgZ2VudHJpZmljYXRpb24gaW4gdGhlIGNpdHleNF4uIEdlbnRyaWZpY2F0aW9uIGlzIGRlZmluZWQgYXMgYSB0eXBlIG9mIG5laWdoYm9yaG9vZCBjaGFuZ2Ugd2hlcmUgaGlnaGVyIGluY29tZSByZXNpZGVudHMgcmVwbGFjZSBsb3dlciBpbmNvbWUgb25lcyDigJMgaG91c2luZyBjb3N0cyByaXNlLCBwcm9wZXJ0eSB2YWx1ZXMgcmlzZSwgYW5kIHR5cGljYWxseSwgbG9uZyB0ZXJtIHJlc2lkZW50cyBnZXQgZGlzcGxhY2VkXjVeLiBHZW50cmlmaWNhdGlvbiB0b2RheSBpcyBpbmZvcm1lZCBieSBhIGxlZ2FjeSBvZiBkaXNwbGFjZW1lbnQgdGhhdCB3YXMgc3B1cnJlZCBieSDigJ1yZWRldmVsb3BtZW504oCdIGFuZCDigJ11cmJhbiByZW5ld2Fs4oCdIHBsYW5zLiBQaGlsYWRlbHBoaWEgd2FzIGhpdCBlc3BlY2lhbGx5IGhhcmQgYnkgdGhpcyBkaXNwbGFjZW1lbnQgaW4gV2VzdCBQaGlsYWRlbHBoaWEgd2l0aCB1bml2ZXJzaXR5IGV4cGFuc2lvbiBwbGFucyB0aHJvdWdob3V0IHRoZSBzZWNvbmQgaGFsZiBvZiB0aGUgMjB0aCBjZW50dXJ5LgoKSW4gYnJvYWRlciBkaXNjdXNzaW9ucyBhYm91dCBnZW50cmlmaWNhdGlvbiBvbmxpbmUgYW5kIGluIG1lZGlhLCBnZW50cmlmaWNhdGlvbiBjYW4gc2VlbSBsaWtlIHNvbWV0aGluZyB0aGF0IG9jY3VycyB3aGVuIGEgYnJld2VyeSByZXBsYWNlcyBhIGNvcm5lciBzdG9yZSwgb3IgYSBsdXh1cnkgZ3ltIHJlcGxhY2VzIGEgc2luZ2xlLWZhbWlseSBob21lLiBHZW50cmlmaWNhdGlvbiBjYW4gYmUgbWFya2VkIGJ5IG1hbnkgdGhpbmdzLCBhbmQgdGhlIHNpZ25zIGNhbiB2YXJ5IGZvciBkaWZmZXJlbnQgcGVvcGxlIHRoYXQgYSBuZWlnaGJvcmhvb2QgaXMgYmVpbmcgZ2VudHJpZmllZC4gIFRoZXJlIGhhcyBldmVuIGJlZW4gbmV3IHRlY2hub2xvZ3kgbGlrZSBBSSBtb2RlbHMgY3JlYXRlZCB0byBpZGVudGlmeSBzaWducyBvZiBnZW50cmlmaWNhdGlvbiBmcm9tIHRlbXBvcmFsIG1hcHMgbGlrZSBHb29nbGUgU3RyZWV0IFZpZXdeNl4uIFdoaWxlIHRoZSBzaWducyBvZiBnZW50cmlmaWNhdGlvbiBjYW4gYmUgdmlzaWJsZSwgdGhleSBtYXkgYmUgaGFyZCB0byB0cmFjayB0aHJvdWdoIGRhdGEuIFJlc2VhcmNoZXJzIHJlbHkgb24gZGVtb2dyYXBoaWMgZGF0YSB0byBtYWtlIGNvbmNsdXNpb25zIGFib3V0IHRoZSB0eXBlcyBvZiBjaGFuZ2UgdGhhdCB0aGUgbmVpZ2hib3Job29kIGlzIGV4cGVyaWVuY2luZywgbGlrZSByYWNpYWwgY2hhbmdlcywgYWdlIGNoYW5nZXMsIGFuZCBpbmNvbWUgY2hhbmdlcy4KCiMjIERhdGEgZXhwbG9yYXRpb24KClRoaXMgcHJvamVjdCBzZWVrcyB0byBjcmVhdGUgYSBwcmVkaWN0aXZlIG1vZGVsIGZvciBnZW50cmlmaWNhdGlvbiB0aGF0IGNhbiBiZSB1c2VkIGluIHBsYW5uaW5nIHByYWN0aWNlIHRvIGlkZW50aWZ5IGFyZWFzIGF0IHJpc2sgZm9yIGdlbnRyaWZpY2F0aW9uIG9yIGlkZW50aWZ5IGFyZWFzIHVuZGVyZ29pbmcgZ2VudHJpZmljYXRpb24uIFRoaXMgbW9kZWwgdXNlcyBmYWN0b3JzIHRoYXQgdGhlIHRlYW0gYXQgTWFyaWUgQW50b2luZXR0ZSBQcmVkaWN0aW9ucyBoYXZlIHJlc2VhcmNoZWQgYW5kIGJlbGlldmUgYXJlIHJlbGlhYmxlIG1hcmtlcnMgZm9yIGdlbnRyaWZpY2F0aW9uLiBUaGUgdmFyaWFibGVzIGluIG91ciBtb2RlbCBhcmUgY29uc3RydWN0aW9uIHBlcm1pdHMsIHZhY2FudCBwcm9wZXJ0aWVzLCBhZmZvcmRhYmxlIGhvdXNpbmcgbG9jYXRpb25zLCBncmVlbiBzcGFjZXMsIGFuZCBkZW1vbGl0aW9ucy4gVGhlIGRhdGEgd2FzIGNvbGxlY3RlZCBmcm9tIFBoaWxhZGVscGhpYeKAmXMgb3BlbiBkYXRhIHBvcnRhbF43Xi4gQWZ0ZXIgZG93bmxvYWRpbmcgdGhlIHJlbGV2YW50IGRhdGEsIGl0IHdhcyBjbGVhbmVkIHRvIGVuc3VyZSB0aGF0IHZhcmlhYmxlcyB3ZXJlIGNsZWFyIGFuZCB0cmFuc2ZlcmFibGUuIAoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZz1GQUxTRSkKCgppZighcmVxdWlyZShwYWNtYW4pKXtpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKTsgbGlicmFyeShwYWNtYW4pfQpwX2xvYWQoc2YsIHRpZHl2ZXJzZSwgdGlkeWNlbnN1cywgUlNvY3JhdGEsIHZpcmlkaXMsIHNwYXRzdGF0LCByYXN0ZXIsIHNwZGVwLCBGTk4sIGdyaWQsIGdyaWRFeHRyYSwga25pdHIsIGthYmxlRXh0cmEsIGNsYXNzSW50LCBSQ29sb3JCcmV3ZXIsIGdnY29ycnBsb3QsIGdncGxvdDIsIHZpcmlkaXMsIGRwbHlyKQoKIyBmdW5jdGlvbnMKcm9vdC5kaXIgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3VyYmFuU3BhdGlhbC9QdWJsaWMtUG9saWN5LUFuYWx5dGljcy1MYW5kaW5nL21hc3Rlci9EQVRBLyIKc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdXJiYW5TcGF0aWFsL1B1YmxpYy1Qb2xpY3ktQW5hbHl0aWNzLUxhbmRpbmcvbWFzdGVyL2Z1bmN0aW9ucy5yIikKCmBgYAoKCmBgYHtyIG1hcCB0aGVtZSwgcmVzdWx0cyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CgptYXBUaGVtZSA8LSBmdW5jdGlvbihiYXNlX3NpemUgPSAxMikgewogIHRoZW1lKAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dCggY29sb3IgPSAiYmxhY2siKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2LGNvbG91ciA9ICJibGFjayIpLAogICAgcGxvdC5jYXB0aW9uPWVsZW1lbnRfdGV4dChoanVzdD0wKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImJsYWNrIiwgZmlsbD1OQSwgc2l6ZT0yKSwKICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpKQp9CgpwbG90VGhlbWUgPC0gZnVuY3Rpb24oYmFzZV9zaXplID0gMTIpIHsKICB0aGVtZSgKICAgIHRleHQgPSBlbGVtZW50X3RleHQoIGNvbG9yID0gImJsYWNrIiksCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImRhcmtncmVlbiIsIHNpemU9MTUsIGZhY2U9ImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZT0iaXRhbGljIiksCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3Q9MCksCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoImdyZXk4MCIsIHNpemUgPSAwLjEpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTIpLAogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXk4MCIsIGNvbG9yID0gIndoaXRlIiksCiAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJibGFjayIsIGZhY2UgPSAiaXRhbGljIiksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSAiYmxhY2siLCBmYWNlID0gIml0YWxpYyIpLAogICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCkKICApCn0KYGBgCgojIyMgUmVhZCBpbiBEYXRhIGZyb20gUGhpbGFkZWxwaGlhCgpgYGB7ciByZWFkX2RhdGEsIHJlc3VsdHMgPSAnaGlkZScsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQojIHBlcm1pdHMKcGhsUGVybWl0cyA8LSAKICBzdF9yZWFkKCJodHRwczovL3BobC5jYXJ0by5jb20vYXBpL3YyL3NxbD9xPVNFTEVDVCsqK0ZST00rcGVybWl0cytXSEVSRStwZXJtaXRpc3N1ZWRhdGUrPj0rJzIwMTMtMDEtMDEnK0FORCtwZXJtaXRpc3N1ZWRhdGUrPCsnMjAyMi0xMi0zMScmZmlsZW5hbWU9cGVybWl0cyZmb3JtYXQ9Z2VvanNvbiZza2lwZmllbGRzPWNhcnRvZGJfaWQiKSAlPiUgCiAgICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyNzI4JyklPiUKICBtdXRhdGUoTGVnZW5kID0gIlBoaWxhZGVscGhpYSBQZXJtaXRzIiklPiUKICBtdXRhdGUoWWVhciA9IGFzLmludGVnZXIoZm9ybWF0KHBlcm1pdGlzc3VlZGF0ZSwgIiVZIikpKSAKCiMjIDIuIFBoaWxhZGVscGhpYSBCb3VuZGFyaWVzCnBobEJvdW5kYXJ5IDwtIAogIHN0X3JlYWQoImh0dHA6Ly9kYXRhLnBobC5vcGVuZGF0YS5hcmNnaXMuY29tL2RhdGFzZXRzLzQwNWVjM2RhOTQyZDRlMjA4NjlkNGUxNDQ5YTJiZTQ4XzAuZ2VvanNvbiIpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKQoKI2R1bm5vIGlmIHdlIG5lZWQgdG8gZmlzaG5ldCB0aGlzCnBobEZpc2huZXQgPC0gCiAgc3RfbWFrZV9ncmlkKHBobEJvdW5kYXJ5LAogICAgICAgICAgICAgICBjZWxsc2l6ZSA9IDUwMCwgCiAgICAgICAgICAgICAgIHNxdWFyZSA9IFRSVUUpICU+JQogIC5bcGhsQm91bmRhcnldICU+JSAKICBzdF9zZigpICU+JQogIG11dGF0ZSh1bmlxdWVJRCA9IHJvd25hbWVzKC4pKQoKIyMgMy4gUGhpbGFkZWxwaGlhIE5laWdoYm9yaG9vZHMgLSBpc24ndCBsb2FkaW5nCnBoaWxseU5laWdoYm9yaG9vZHMgPC0KICBzdF9yZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vb3BlbmRhdGFwaGlsbHkvb3Blbi1nZW8tZGF0YS9tYXN0ZXIvcGhpbGFkZWxwaGlhLW5laWdoYm9yaG9vZHMvcGhpbGFkZWxwaGlhLW5laWdoYm9yaG9vZHMuZ2VvanNvbiIpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUKICBzdF90cmFuc2Zvcm0oc3RfY3JzKHBobEZpc2huZXQpKSAKCiMjIDQuIFZhY2FudCBQcm9wZXJ0eQp2YWNhbnRCdWlsZGluZyA8LQogIHN0X3JlYWQoJ2h0dHBzOi8vb3BlbmRhdGEuYXJjZ2lzLmNvbS9kYXRhc2V0cy9mN2VkNjgyOTNjNWU0MGQ1OGYxZGU5Yzg0MzVjM2U4NF8wLmdlb2pzb24nKSAlPiUgCiAgbmEub21pdCgpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUKICBzdF90cmFuc2Zvcm0oc3RfY3JzKHBobEZpc2huZXQpKSAlPiUKICBtdXRhdGUoTGVnZW5kID0gIlZhY2FudCBCdWlsZGluZ3MiKQoKIyMgNS4gQWZmb3JkYWJsZSBIb3VzaW5nCmFmZm9yZGFibGVIb3VzaW5nIDwtCiAgc3RfcmVhZCgnaHR0cHM6Ly9vcGVuZGF0YS5hcmNnaXMuY29tL2RhdGFzZXRzL2NhODk0NDE5MGI2MDRiMmFhZTdlZmYxN2M4ZGQ5ZWY1XzAuZ2VvanNvbicpICU+JSAKICBmaWx0ZXIoRklTQ0FMX1lFQVJfQ09NUExFVEUgPj0gIjIwMTIiKSAlPiUKICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyNzI4JykgJT4lCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhwaGxGaXNobmV0KSkgJT4lCiAgbXV0YXRlKExlZ2VuZCA9ICJBZmZvcmRhYmxlIEhvdXNpbmciKQoKCiMjIDcuIEdyZWVuIFNwYWNlcwpncmVlblNwYWNlIDwtCiAgc3RfcmVhZCgnaHR0cHM6Ly9vcGVuZGF0YS5hcmNnaXMuY29tL2RhdGFzZXRzL2Q1MjQ0NTE2MGFiMTQzODBhNjczZTU4NDkyMDNlYjY0XzAuZ2VvanNvbicpICU+JSAKICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyNzI4JykgJT4lCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhwaGxGaXNobmV0KSkgJT4lCiAgbXV0YXRlKExlZ2VuZCA9ICJHcmVlbiBTcGFjZXMiKQoKCiMjIDEwLiBEZW1vbGl0aW9uIERhdGEKYnVpbGRpbmdEZW1vbGl0aW9uIDwtCiAgc3RfcmVhZCgnaHR0cHM6Ly9waGwuY2FydG8uY29tL2FwaS92Mi9zcWw/cT1TRUxFQ1QrKitGUk9NK2RlbW9saXRpb25zJmZpbGVuYW1lPWRlbW9saXRpb25zJmZvcm1hdD1nZW9qc29uJnNraXBmaWVsZHM9Y2FydG9kYl9pZCcpICU+JSAKICBtdXRhdGUoeWVhciA9IHN1YnN0cihzdGFydF9kYXRlLDEsNCkpICU+JQogIGZpbHRlcih5ZWFyID09ICcyMDIyJykgJT4lCiAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjcyOCcpICU+JQogIHN0X3RyYW5zZm9ybShzdF9jcnMocGhsRmlzaG5ldCkpICU+JQogIG11dGF0ZShMZWdlbmQgPSAiQnVpbGRpbmcgRGVtb2xpdGlvbiIpIAoKIyMgMTEuIENlbnN1cyBkYXRhIC0gQUNTIDIwMjEKdHJhY3RzMjIgPC0gZ2V0X2FjcygKICBnZW9ncmFwaHkgPSAidHJhY3QiLAogIHZhcmlhYmxlcyA9IGMoCiAgICAiQjAxMDAzXzAwMSIsICAgIyBUb3RhbCBQb3B1bGF0aW9uCiAgICAiQjE5MDEzXzAwMSIsICAgIyBNZWRpYW4gSG91c2Vob2xkIEluY29tZQogICAgIkIyNTAwOF8wMDIiLCAgICMgT3duZXItT2NjdXBpZWQgVW5pdHMKICAgICJCMjUwMDhfMDAzIiwgICAjIFJlbnRlci1PY2N1cGllZCBVbml0cwogICAgIkIyNTAwOF8wMDFFIiwgICMgVG90YWwgUG9wdWxhdGlvbiBpbiBIb3VzaW5nIFVuaXRzCiAgICAiQjE1MDAzXzAyMiIsICAgIyBFZHVjYXRpb25hbCBBdHRhaW5tZW50OiBCYWNoZWxvcidzIERlZ3JlZQogICAgIkIwNjAxMl8wMDJFIiwgICMgUG9wdWxhdGlvbiBCZWxvdyB0aGUgUG92ZXJ0eSBMZXZlbAogICAgIkIwMjAwMV8wMDIiLCAgICMgUmFjZSBhbmQgRXRobmljaXR5OiBXaGl0ZSBBbG9uZQogICAgIkIwMjAwMV8wMDMiLCAgICMgUmFjZSBhbmQgRXRobmljaXR5OiBCbGFjayBvciBBZnJpY2FuIEFtZXJpY2FuIEFsb25lCiAgICAiQjI3MDExXzAwOEUiICAjIFBvcHVsYXRpb24gVW5lbXBsb3llZAogICksCiAgeWVhciA9IDIwMjIsCiAgc3RhdGUgPSAiUEEiLAogIGNvdW50eSA9ICJQaGlsYWRlbHBoaWEiLAogIGdlb21ldHJ5ID0gVFJVRSwKICBvdXRwdXQgPSAid2lkZSIKKSU+JQogIGRwbHlyOjpzZWxlY3QoLU5BTUUsIC1lbmRzX3dpdGgoIk0iKSkgJT4lCiAgcmVuYW1lKHRvdGFsUG9wID0gQjAxMDAzXzAwMUUsICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBUb3RhbCBQb3B1bGF0aW9uCiAgICAgICAgIG1lZEhISW5jID0gQjE5MDEzXzAwMUUsICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBNZWRpYW4gSG91c2Vob2xkIEluY29tZQogICAgICAgICB0b3RhbFVuaXQgPSBCMjUwMDhfMDAxRSwgICAgICAgICAgICAgICAgICAgICAgICAgICMgVG90YWwgUG9wdWxhdGlvbiBpbiBIb3VzaW5nIFVuaXRzCiAgICAgICAgIG93bmVyT2NjdXBpZWQgPSBCMjUwMDhfMDAyRSwgICAgICAgICAgICAgICAgICAgICAgIyBPd25lci1PY2N1cGllZCBVbml0cwogICAgICAgICByZW50ZXJPY2N1cGllZCA9IEIyNTAwOF8wMDNFLCAgICAgICAgICAgICAgICAgICAgICMgUmVudGVyLU9jY3VwaWVkIFVuaXRzCiAgICAgICAgIGJhY2hEZWdyZWUgPSBCMTUwMDNfMDIyRSwgICAgICAgICAgICAgICAgICAgICMgRWR1Y2F0aW9uYWwgQXR0YWlubWVudDogQmFjaGVsb3IncyBEZWdyZWUKICAgICAgICAgdG90YWxQb3YgPSBCMDYwMTJfMDAyRSwgICAgICAgICAgICAgICAgICAgICAgICMgUG9wdWxhdGlvbiBCZWxvdyB0aGUgUG92ZXJ0eSBMZXZlbAogICAgICAgICB0b3RhbFVuZW1wbG95ID0gQjI3MDExXzAwOEUsICAgICAgICAgICAgICAgICAgIyBQb3B1bGF0aW9uIFVuZW1wbG95ZWQKICAgICAgICAgd2hpdGVBbG9uZSA9IEIwMjAwMV8wMDJFLCAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSYWNlIGFuZCBFdGhuaWNpdHk6IFdoaXRlIEFsb25lCiAgICAgICAgIGJsYWNrQWxvbmUgPSBCMDIwMDFfMDAzRSAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSYWNlIGFuZCBFdGhuaWNpdHk6IEJsYWNrIG9yIEFmcmljYW4gQW1lcmljYW4gQWxvbmUKICAgICAgICAgKQoKIyMjIDExLjEuIFRyYW5zZm9ybSB0aGUgZGF0YSB0byBFU1JJOjEwMjcyOCBwcm9qZWN0aW9uCgp0cmFjdHMyMiA8LSB0cmFjdHMyMiAlPiUgc3RfdHJhbnNmb3JtKHN0X2NycyhwaGxGaXNobmV0KSkKCiMjIyAxMS4yIENyZWF0ZSBuZXcgdmFyaWFibGVzCgp0cmFjdHMyMiA8LSB0cmFjdHMyMiAlPiUKICBtdXRhdGUocGN0V2hpdGUgPSBpZmVsc2UodG90YWxQb3AgPiAwLCB3aGl0ZUFsb25lIC8gdG90YWxQb3AgKiAxMDAsMCksCiAgICAgICAgIHBjdEJsYWNrID0gaWZlbHNlKHRvdGFsUG9wID4gMCwgYmxhY2tBbG9uZSAvIHRvdGFsUG9wICogMTAwLDApLAogICAgICAgICBwY3RQb3YgPSBpZmVsc2UodG90YWxQb3AgPiAwLCB0b3RhbFBvdiAvIHRvdGFsUG9wICoxMDAsIDApLAogICAgICAgICBwY3RVbmVtcGxveSA9IGlmZWxzZSh0b3RhbFBvcCA+IDAsIHRvdGFsVW5lbXBsb3kgLyB0b3RhbFBvcCAqMTAwLCAwKSwKICAgICAgICAgcGN0QmFjaCA9IGlmZWxzZSh0b3RhbFBvcCA+IDAsIGJhY2hEZWdyZWUgLyB0b3RhbFBvcCAqMTAwLCAwKSwKICAgICAgICAgcGN0T3duZXJPY2N1cGllZCA9IGlmZWxzZSh0b3RhbFBvcCA+IDAsIG93bmVyT2NjdXBpZWQgLyB0b3RhbFVuaXQgKjEwMCwgMCksCiAgICAgICAgIHBjdFJlbnRlck9jY3VwaWVkID0gaWZlbHNlKHRvdGFsUG9wID4gMCwgcmVudGVyT2NjdXBpZWQgLyB0b3RhbFVuaXQgKjEwMCwgMCkKICAgICAgICAgKSAlPiUKICBkcGx5cjo6c2VsZWN0KC13aGl0ZUFsb25lLCAtYmxhY2tBbG9uZSwgLXRvdGFsUG92ICwtdG90YWxVbmVtcGxveSwtb3duZXJPY2N1cGllZCwgLXJlbnRlck9jY3VwaWVkLCAtdG90YWxVbml0LCAtYmFjaERlZ3JlZSwgLUdFT0lEKSAlPiUKICBzdF90cmFuc2Zvcm0oc3RfY3JzKHBobEZpc2huZXQpKSAKCiMjIyAxMS4zIE9yZ2FuaXplIGludG8gZGF0YXNldHMgLSB3aGF0IGlzIHRoaXMgd2hvbGUgb3JnYW5pemF0aW9uIHBhcnQgZm9yPz8KCnRyYWN0czIyLm1lZEhISW5jIDwtIHRyYWN0czIyICU+JQogIGRwbHlyOjpzZWxlY3QobWVkSEhJbmMpICU+JQogIHJlbmFtZShMZWdlbmQgPSBtZWRISEluYykKCnRyYWN0czIyLnBjdFdoaXRlIDwtIHRyYWN0czIyICU+JQogIGRwbHlyOjpzZWxlY3QocGN0V2hpdGUpJT4lCiAgcmVuYW1lKExlZ2VuZCA9IHBjdFdoaXRlKQoKdHJhY3RzMjIucGN0QmxhY2sgPC0gdHJhY3RzMjIgJT4lCiAgZHBseXI6OnNlbGVjdChwY3RCbGFjayklPiUKICByZW5hbWUoTGVnZW5kID0gcGN0QmxhY2spCgp0cmFjdHMyMi5wY3RQb3YgPC0gdHJhY3RzMjIgJT4lCiAgZHBseXI6OnNlbGVjdChwY3RQb3YpJT4lCiAgcmVuYW1lKExlZ2VuZCA9IHBjdFBvdikKCnRyYWN0czIyLnBjdFVuZW1wbG95IDwtIHRyYWN0czIyICU+JQogIGRwbHlyOjpzZWxlY3QocGN0VW5lbXBsb3kpJT4lCiAgcmVuYW1lKExlZ2VuZCA9IHBjdFVuZW1wbG95KQoKdHJhY3RzMjIucGN0QmFjaCA8LSB0cmFjdHMyMiAlPiUKICAgZHBseXI6OnNlbGVjdChwY3RCYWNoKSU+JQogICByZW5hbWUoTGVnZW5kID0gcGN0QmFjaCkKCnRyYWN0czIyLnBjdE93bmVyT2NjdXBpZWQgPC0gdHJhY3RzMjIgJT4lCiAgZHBseXI6OnNlbGVjdChwY3RPd25lck9jY3VwaWVkKSU+JQogIHJlbmFtZShMZWdlbmQgPSBwY3RPd25lck9jY3VwaWVkKQoKdHJhY3RzMjIucGN0UmVudGVyT2NjdXBpZWQgPC0gdHJhY3RzMjIgJT4lCiAgZHBseXI6OnNlbGVjdChwY3RSZW50ZXJPY2N1cGllZCklPiUKICByZW5hbWUoTGVnZW5kID0gcGN0UmVudGVyT2NjdXBpZWQpCmBgYAoKIyMjIERhdGEgVmlzdWFsaXNhdGlvbgoKVGhlIGRhdGEgd2FzIHZpc3VhbGl6ZWQgdXNpbmcgbWFwcGluZyB0b29scy4gVGhlIGFyZWEgZnJvbSBhcm91bmQgQ2VudGVyIENpdHkgdG8gbG93ZXIgTm9ydGggUGhpbGFkZWxwaGlhIGFuZCBLZW5zaW5ndG9uIGhhcyB0aGUgZGVuc2VzdCBhbW91bnQgb2YgY29uc3RydWN0aW9uIHBlcm1pdHMgYW5kIGRlbW9saXRpb24gcGVybWl0cy4gVGhlcmUgaXMgYSBoaWdoIGRlbnNpdHkgb2YgdmFjYW50IGJ1aWxkaW5ncyBpbiBXZXN0IGFuZCBOb3J0aCBQaGlsYWRlbHBoaWEuCgpgYGB7ciBwb2xpY2UgZGF0YSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIHJlc3VsdHMgPSAnaGlkZSd9CgojQ2F0ZWdvcml6aW5nIHRoZSBwZXJtaXRzIGZvciBjb25zdHJ1Y3Rpb24gYW5kIGRlbW9saXRpb24KcGhsUGVybWl0cyA8LSBwaGxQZXJtaXRzICU+JQogIG11dGF0ZShuZXdUeXBlID0gY2FzZV93aGVuKHBlcm1pdHR5cGUgPT0gIkJVSUxESU5HIiB8IHBlcm1pdHR5cGUgPT0gIkJQX05FV0NOU1QiICB+ICdDT05TVFJVQ1RJT04gUEVSTUlUJywKICBwZXJtaXR0eXBlID09ICJERU1PTElUSU9OIiB8IHBlcm1pdHR5cGUgPT0gIkJQX0RFTU8iIH4gJ0RFTU9MSVRJT04gUEVSTUlUJykpCgoKY25zdFBlcm1pdHMgPC0gcGhsUGVybWl0cyAlPiUKICBmaWx0ZXIobmV3VHlwZSA9PSAnQ09OU1RSVUNUSU9OIFBFUk1JVCcpCgpkZW1vUGVybWl0cyA8LSBwaGxQZXJtaXRzICU+JQogIGZpbHRlcihuZXdUeXBlID09ICdERU1PTElUSU9OIFBFUk1JVCcpCgpjbnN0UGVybWl0c18yMDIyIDwtIGNuc3RQZXJtaXRzJT4lCiAgZmlsdGVyKFllYXI9PTIwMjIpCgpjbnN0UGVybWl0c18yMDEzIDwtIGNuc3RQZXJtaXRzJT4lCiAgZmlsdGVyKFllYXIgPT0gMjAxMykKYGBgCgpUaGUgcGxvdHMgYmVsb3cgc2hvdyBob3cgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBwZXJtaXRzIGFyZSBsb2NhdGVkIGFsbCBvdmVyIFBoaWxhZGVscGhpYSwgbG9va2luZyBwcmltYXJpbHkgYXQgdGhlIHRoZSBwZXJtaXRzIHRoYXQgZmFsbCB1bmRlciBjb25zdHJ1Y3Rpb24gb3Igem9uaW5nLiBXZSBhbHNvIGxvb2sgYXQgaG93IG90aGVyIHZhcmlvdXMgY2hvc2VuIGRlbW9ncmFwaGljIGFuZCBzb2Npby1lY29ub21pYyBmYWN0b3JzIGFyZSBtYXBwZWQgb3V0IGFjcm9zcyB0aGUgY2l0eSB0byBjb21wYXJlIHRoZW0gYWdhaW5zdCB3aGVyZSB0aGVyZSBpcyB0aGUgaGlnaGVzdCBkZW5zaXR5IG9mIHBlcm1pdHMuIENvbnN0cnVjdGlvbiBwZXJtaXRzIGNhbiBiZSBzZWVuIGluIGhpZ2hlciBjb25jZW50cmF0aW9ucyBpbiB0aGUgYXJlYXMgd2hlcmUgdGhlcmUgaXMgYSBoaWdoZXIgcGVyY2VudGFnZSBvZiBpbmRpdmlkdWFscyB3aXRoIGJhY2hlbG9yJ3MgZGVncmVlcyBhbmQgaGlnaGVyIHVuZW1wbG95bWVudC4KCmBgYHtyIHBsb3QgdmFyaWFibGVzLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD00LCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KCiMgUGxvdCAxOiBtYXAgb2YgYWxsIGNvbnN0cnVjdGlvbiBhbmQgZGVtbyBwZXJtaXRzIGlzc3VlZCBiL3cgMjAxMyBhbmQgMjAyMgpnZ3Bsb3QoKSArIAogICAgZ2VvbV9zZihkYXRhID0gcGhsQm91bmRhcnksIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArICAKICAgIGdlb21fc2YoZGF0YSA9IHBobFBlcm1pdHMsIGFlcyhjb2xvdXIgPSBuZXdUeXBlKSwgc2l6ZSA9IDAuNSwgc2hvdy5sZWdlbmQgPSAicG9pbnQiKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIm1hZ2VudGEiLCAibGlnaHRncmVlbiIpKSArCiAgbGFicyh0aXRsZSA9ICJNYWpvciBQZXJtaXRzIElzc3VlZCwgMjAxMy0yMiBpbiBQaGlsYWRlbHBoaWEiLAogICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgMSIpICsKICAgIHRoZW1lX3ZvaWQoKSAgCgoKICAjIFBsb3QgMiArIDM6IE1hcHBlZCBwb2ludHMgYW5kIERlbnNpdHkgbWFwIG9mIGNvbnN0cnVjdGlvbiBwZXJtaXRzIGlzc3VlZApnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IHBobEJvdW5kYXJ5LCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKyAgCiAgZ2VvbV9zZihkYXRhID0gY25zdFBlcm1pdHMsIGFlcyhjb2xvciA9ICJtYWdlbnRhIiksIHNpemUgPSAwLjUpICsgCiAgc2NhbGVfY29sb3JfaWRlbnRpdHkoZ3VpZGUgPSAibGVnZW5kIiwgbmFtZSA9IE5VTEwsIGxhYmVscyA9ICJDb25zdHJ1Y3Rpb24gUGVybWl0cyIpICsKICBsYWJzKHRpdGxlID0gIkNvbnN0cnVjdGlvbiBQZXJtaXRzIElzc3VlZCwgMjAxMy0yMiBpbiBQaGlsYWRlbHBoaWEiLAogICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgMiIpICsKICB0aGVtZV92b2lkKCkgCgoKICBnZ3Bsb3QoKSArIAogICAgZ2VvbV9zZihkYXRhID0gcGhsQm91bmRhcnksIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArICAKICAgIHN0YXRfZGVuc2l0eTJkKGRhdGEgPSBkYXRhLmZyYW1lKHN0X2Nvb3JkaW5hdGVzKGNuc3RQZXJtaXRzKSksICAKICAgICAgICAgICAgICAgICAgIGFlcyhYLCBZLCBmaWxsID0gLi5sZXZlbC4uLCBhbHBoYSA9IC4ubGV2ZWwuLiksICAKICAgICAgICAgICAgICAgICAgIHNpemUgPSAwLjAxLCBiaW5zID0gNDAsIGdlb20gPSAncG9seWdvbicpICsgIAogICAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0icm9ja2V0IikgKyAgCiAgICBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgQ29uc3RydWN0aW9uIFBlcm1pdHMiKSArICAKICAgIHRoZW1lX3ZvaWQoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgIAoKCgojUGxvdCA0ICsgNTogTWFwcGVkIHBvaW50cyBhbmQgRGVuc2l0eSBtYXAgb2YgZGVtb2xpdGlvbiBwZXJtaXRzIGlzc3VlZApnZ3Bsb3QoKSArIAogICAgZ2VvbV9zZihkYXRhID0gcGhsQm91bmRhcnksIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArICAKICAgIGdlb21fc2YoZGF0YSA9IGRlbW9QZXJtaXRzLCBhZXMoY29sb3VyID0gImxpZ2h0Z3JlZW4iKSwgc2l6ZSA9IDAuNSkgKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KGd1aWRlID0gImxlZ2VuZCIsIG5hbWUgPSBOVUxMLCBsYWJlbHMgPSAiRGVtb2xpdGlvbiBQZXJtaXRzIikgKwogIGxhYnModGl0bGUgPSAiRGVtb2xpdGlvbiBQZXJtaXRzIElzc3VlZCwgMjAxMy0yMiBpbiBQaGlsYWRlbHBoaWEiLAogICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgNCIpICsKICAgIHRoZW1lX3ZvaWQoKQoKZ2dwbG90KCkgKyAKICAgIGdlb21fc2YoZGF0YSA9IHBobEJvdW5kYXJ5LCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKyAgCiAgICBzdGF0X2RlbnNpdHkyZChkYXRhID0gZGF0YS5mcmFtZShzdF9jb29yZGluYXRlcyhkZW1vUGVybWl0cykpLCAgCiAgICAgICAgICAgICAgICAgICBhZXMoWCwgWSwgZmlsbCA9IC4ubGV2ZWwuLiwgYWxwaGEgPSAuLmxldmVsLi4pLCAgCiAgICAgICAgICAgICAgICAgICBzaXplID0gMC4wMSwgYmlucyA9IDQwLCBnZW9tID0gJ3BvbHlnb24nKSArICAKICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJyb2NrZXQiKSArICAKICAgIHNjYWxlX2FscGhhKHJhbmdlID0gYygwLjAwLCAwLjM1KSwgZ3VpZGUgPSBGQUxTRSkgKyAgCiAgICBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgQ29uc3RydWN0aW9uIFBlcm1pdHMiKSArICAKICAgIHRoZW1lX3ZvaWQoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgIAoKCiMgcGxvdCA2OiBhZmZvcmRhYmxlIGhvdXNpbmcKZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSBwaGxCb3VuZGFyeSwgZmlsbCA9ICJncmV5ODkiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsgIAogIGdlb21fc2YoZGF0YSA9IGFmZm9yZGFibGVIb3VzaW5nLCBhZXMoY29sb3IgPSAib3JhbmdlIiksIHNpemUgPSAwLjUpICsgCiAgc2NhbGVfY29sb3JfaWRlbnRpdHkoZ3VpZGUgPSAibGVnZW5kIiwgbmFtZSA9IE5VTEwsIGxhYmVscyA9ICJBZmZvcmRhYmxlIEhvdXNpbmcgVW5pdHMiKSArCiAgbGFicyh0aXRsZSA9ICJBZmZvcmRhYmxlIEhvdXNpbmcgRGV2ZWxvcG1lbnRzLCAyMDEzLTIyIGluIFBoaWxhZGVscGhpYSIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSA2IikgKwogIHRoZW1lX3ZvaWQoKSAKCiNwbG90NzogCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gcGhsQm91bmRhcnksIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArICAKICBnZW9tX3NmKGRhdGEgPSBncmVlblNwYWNlLCBhZXMoY29sb3IgPSAiZGFya2dyZWVuIiksIHNpemUgPSAwLjUpICsgCiAgc2NhbGVfY29sb3JfaWRlbnRpdHkoZ3VpZGUgPSAibGVnZW5kIiwgbmFtZSA9IE5VTEwsIGxhYmVscyA9ICJHcmVlbiBTcGFjZXMiKSArCiAgbGFicyh0aXRsZSA9ICJHcmVlbiBTcGFjZXMsIDIwMTMtMjIgaW4gUGhpbGFkZWxwaGlhIiwKICAgICAgIGNhcHRpb24gPSAiRmlndXJlIDciKSArCiAgdGhlbWVfdm9pZCgpIAoKCiNwbG90OCBhbmQgOTogdmFjYW50IGxhbmQgcG9pbnQgYW5kIGRlbnNpdHkgbWFwcwpnZ3Bsb3QoKSArIAogICAgZ2VvbV9zZihkYXRhID0gcGhsQm91bmRhcnksIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArICAKICAgIGdlb21fc2YoZGF0YSA9IHZhY2FudEJ1aWxkaW5nLCBhZXMoY29sb3IgPSAnZGFya3JlZCcpLCBzaXplID0gMC41LCBzaG93LmxlZ2VuZCA9ICJwb2ludCIpICsKICBzY2FsZV9jb2xvcl9pZGVudGl0eShndWlkZSA9ICJsZWdlbmQiLCBuYW1lID0gTlVMTCwgbGFiZWxzID0gIlZhY2FudCBCdWlsZGluZ3MiKSArCiAgbGFicyh0aXRsZSA9ICJTdXNwZWN0ZWQgVmFjYW50IEJ1aWxkaW5ncyBpbiBQaGlsYWRlbHBoaWEiLAogICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgOCIpICsKICAgIHRoZW1lX3ZvaWQoKQoKZ2dwbG90KCkgKyAKICAgIGdlb21fc2YoZGF0YSA9IHBobEJvdW5kYXJ5LCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKyAgCiAgICBzdGF0X2RlbnNpdHkyZChkYXRhID0gZGF0YS5mcmFtZShzdF9jb29yZGluYXRlcyh2YWNhbnRCdWlsZGluZykpLCAgCiAgICAgICAgICAgICAgICAgICBhZXMoWCwgWSwgZmlsbCA9IC4ubGV2ZWwuLiwgYWxwaGEgPSAuLmxldmVsLi4pLCAgCiAgICAgICAgICAgICAgICAgICBzaXplID0gMC4wMSwgYmlucyA9IDQwLCBnZW9tID0gJ3BvbHlnb24nKSArICAKICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJyb2NrZXQiKSArICAKICAgIHNjYWxlX2FscGhhKHJhbmdlID0gYygwLjAwLCAwLjM1KSwgZ3VpZGUgPSBGQUxTRSkgKyAKICAgIGxhYnModGl0bGUgPSAiRGVuc2l0eSBvZiBWYWNhbnQgQnVsZGluZ3MiKSArICAKICAgIHRoZW1lX3ZvaWQoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgIAoKCgojcGxvdDEwOiBkZW1vZ3JhcGhpYwpnZ3Bsb3QoKSsKICAgIGdlb21fc2YoZGF0YT10cmFjdHMyMiwgYWVzKGNvbG9yPU5BLCBmaWxsPXBjdFVuZW1wbG95KSkrCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzKCkrCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpKwogICAgZ2VvbV9zZihkYXRhID0gY25zdFBlcm1pdHMsIGFlcyhjb2xvcj0ieWVsbG93IikgLAogICAgICAgICAgc2hvdy5sZWdlbmQgPSAicG9pbnQiLCBzaXplID0gLjEsIGFscGhhPTAuMykgKwogICAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArCiAgICBsYWJzKHRpdGxlPSIlIFVuZW1wbG95bWVudCBhcm91bmQgUGVybWl0cyBJc3N1ZWQiKSsKICAgIG1hcFRoZW1lKCkrdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLCBsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKQoKZ2dwbG90KCkrCiAgICBnZW9tX3NmKGRhdGE9dHJhY3RzMjIsIGFlcyhjb2xvcj1OQSwgZmlsbD1wY3RCYWNoKSkrCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzKCkrCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMoKSsKICAgIGdlb21fc2YoZGF0YSA9IGNuc3RQZXJtaXRzLCBhZXMoY29sb3I9InllbGxvdyIpICwKICAgICAgICAgIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC4xLCBhbHBoYT0wLjMpICsKICAgIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkgKwogICAgbGFicyh0aXRsZT0iJSBQb3B1bGF0aW9uIHdpdGggQmFjaGVsb3IncyBEZWdyZWUgYXJvdW5kIFBlcm1pdHMgSXNzdWVkIikKYGBgCiMjIyBGaXNobmV0CgpIZXJlIHdlIGNvbnN0cnVjdCBhIGdlb3NwYXRpYWwgZGF0YXNldCB0byBleGFtaW5lIGhvdyB0aGUgcmlzayBvZiBuZXcgZGV2ZWxvcG1lbnQgaXMgc3ByZWFkIHRocm91Z2hvdXQgUGhpbGFkZWxwaGlhLiBUbyBkbyB0aGlzIHdlIGNyZWF0ZWQgYSBmaXNobmV0IHdoaWNoIGlzIGEgY29udGlub3VzIGdyaWQgcGF0dGVybiBjb25zaXN0aW5nIG9mIGNlbGxzIG1lYXN1cmluZyA1MDB4NTAwIGZlZXQuIFRoaXMgZ3JpZCBhbGxvd3MgdXMgdG8gY29uY2VwdHVhbGl6ZSBkZXZlbG9wbWVudCByaXNrLCByZXByZXNlbnRlZCBieSB0aGUgcHJlc2VuY2Ugb2YgbmV3IGNvbnN0cnVjdGlvbiBwZXJtaXRzLiBUaGUgZm9sbG93aW5nIHBsb3QgZGVwaWN0cyB0aGUgY291bnQgb2YgbmV3IGNvbnN0cnVjdGlvbiBwZXJtaXRzIGFjcm9zcyB0aGUgZmlzaG5ldCwgd2l0aCBncmlkIGNlbGxzIGRlcGljdGVkIGluIHllbGxvdyBpbmRpY2F0aW5nIHRoZSBoaWdoZXN0IHBlcm1pdCBjb3VudHMuCgpgYGB7ciBmaXNobmV0LCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KCiMgQXR0YWNoaW5nIGRhdGFzZXRzIG9uIHNwYXRpYWwgZmFjdG9ycyB0byBGaXNobmV0CgojIyAxLiBFeHRyYWN0aW5nIGdlb21ldHJ5IGZvciBzcGF0aWFsIGZhY3RvcnMKCgphZmZvcmRhYmxlSG91c2luZ3MgPC0gYWZmb3JkYWJsZUhvdXNpbmcgJT4lCiAgZHBseXI6OnNlbGVjdChnZW9tZXRyeSwgTGVnZW5kKQoKdmFjYW50QnVpbGRpbmdzIDwtIHZhY2FudEJ1aWxkaW5nICU+JQogIGRwbHlyOjpzZWxlY3QoZ2VvbWV0cnksIExlZ2VuZCkKCmdyZWVuU3BhY2VzIDwtIGdyZWVuU3BhY2UgJT4lCiAgZHBseXI6OnNlbGVjdChnZW9tZXRyeSwgTGVnZW5kKQoKYnVpbGRpbmdEZW1vbGl0aW9ucyA8LSBidWlsZGluZ0RlbW9saXRpb24gJT4lCiAgZHBseXI6OnNlbGVjdChnZW9tZXRyeSwgTGVnZW5kKQoKCiMjIDIuIENyZWF0aW5nIGZpc2huZXQgb2Ygc3BhdGlhbCBmYWN0b3IgdmFyaWFibGVzIAoKdmFyc19uZXQgPC0gCiAgcmJpbmQoYWZmb3JkYWJsZUhvdXNpbmdzLCB2YWNhbnRCdWlsZGluZ3MsCiAgICAgICAgZ3JlZW5TcGFjZXMsIGJ1aWxkaW5nRGVtb2xpdGlvbnMpICU+JQogIHN0X2pvaW4oLiwgcGhsRmlzaG5ldCwgam9pbj1zdF93aXRoaW4pICU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBncm91cF9ieSh1bmlxdWVJRCwgTGVnZW5kKSAlPiUKICBzdW1tYXJpemUoY291bnQgPSBuKCkpICU+JQogIGZ1bGxfam9pbihwaGxGaXNobmV0LCBieSA9ICJ1bmlxdWVJRCIpICU+JQogIHNwcmVhZChMZWdlbmQsIGNvdW50LCBmaWxsPTApICU+JQogIHN0X3NmKCkgJT4lCiAgbmEub21pdCgpICU+JSAKICBkcGx5cjo6c2VsZWN0KC1gPE5BPmApICU+JQogIHVuZ3JvdXAoKQoKCmNuc3RQZXJtaXRzIDwtIHN0X3RyYW5zZm9ybShjbnN0UGVybWl0cywgc3RfY3JzKHBobEZpc2huZXQpKQoKY29uc3RydWN0aW9uX25ldCA8LSAKICBkcGx5cjo6c2VsZWN0KGNuc3RQZXJtaXRzKSAlPiUgCiAgbXV0YXRlKGNvdW50UGVybWl0cyA9IDEpICU+JSAKICBhZ2dyZWdhdGUoLiwgcGhsRmlzaG5ldCwgc3VtKSAlPiUKICBtdXRhdGUoY291bnRQZXJtaXRzID0gcmVwbGFjZV9uYShjb3VudFBlcm1pdHMsIDApLAogICAgICAgICB1bmlxdWVJRCA9IHJvd25hbWVzKC4pLAogICAgICAgICBjdklEID0gc2FtcGxlKHJvdW5kKG5yb3cocGhsRmlzaG5ldCkgLyAyNCksIAogICAgICAgICAgICAgICAgICAgICAgIHNpemU9bnJvdyhwaGxGaXNobmV0KSwgcmVwbGFjZSA9IFRSVUUpKQoKIyB2aXN1YWxpc2luZyBwZXJtaXRzIGpvaW5lZCB0byBmaXNobmV0CgpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gY29uc3RydWN0aW9uX25ldCwgYWVzKGZpbGwgPSBjb3VudFBlcm1pdHMpLCBjb2xvciA9IE5BKSArCiAgZ2VvbV9zZihkYXRhID0gc3RfYm91bmRhcnkocGhsRmlzaG5ldCksIGNvbG9yID0gImRhcmtyZWQiLCBsd2QgPSAuMDQsIGFscGhhPS4yKSArICMgQWRkIGJvdW5kYXJpZXMgaW4gcmVkCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gInBsYXNtYSIsCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICdDb25zdHJ1Y3Rpb24gQ291bnRzJykgKwogIGxhYnModGl0bGUgPSAiQ29uc3RydWN0aW9uIFBlcm1pdHMgSm9pbmVkIHRvIEZpc2huZXQiLAogICAgICAgc3VidGl0bGUgPSAnUGhpbGFkZWxwaGlhJykgKyBtYXBUaGVtZSgpICsgcGxvdFRoZW1lKCkKYGBgCiMjIyBOZWFyZXN0IE5laWdoYm91cgoKVGhpcyBjb2RlIHNlZ21lbnQgY3JlYXRlcyBhIG5lYXJlc3QgbmVpZ2hib3IgZmVhdHVyZSBmb3IgZGlmZmVyZW50IGNhdGVnb3JpZXMsIGluY2x1ZGluZyB2YWNhbnQgYnVpbGRpbmdzLCBhZmZvcmRhYmxlIGhvdXNpbmcsIGdyZWVuIHNwYWNlcywgYW5kIGJ1aWxkaW5nIGRlbW9saXRpb25zLiBGb3IgZWFjaCBjYXRlZ29yeSwgaXQgZmlyc3QgbWFwcyB0aGUgbmVhcmVzdCBmZWF0dXJlIGZyb20gYSBnaXZlbiBkYXRhc2V0IHRvIGFub3RoZXIgZGF0YXNldCwgY29udmVydHMgdGhlIGRhdGFzZXRzIHRvIGByc2dlb2AgZ2VvbWV0cmllcywgY2FsY3VsYXRlcyB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlIGJldHdlZW4gZWFjaCBvYnNlcnZhdGlvbiBpbiBvbmUgZGF0YXNldCBhbmQgaXRzIG5lYXJlc3QgbmVpZ2hib3IgaW4gdGhlIG90aGVyIGRhdGFzZXQuIEZpbmFsbHksIHRoZSBjYWxjdWxhdGVkIGRpc3RhbmNlcyBhcmUgYXBwZW5kZWQgYXMgbmV3IHZhcmlhYmxlcyB0byB0aGUgb3JpZ2luYWwgZGF0YXNldCBmb3IgZWFjaCBjYXRlZ29yeSwgc3VjaCBhcyBgZGlzdF92YWNhbnRCdWlsZGluZ2AsIGBkaXN0X2FmZm9yZGFibGVIb3VzaW5nYCwgYGRpc3RfZ3JlZW5TcGFjZWAsIGFuZCBgZGlzdF9idWlsZGluZ0RlbW9saXRpb25gLiBFc3NlbnRpYWxseSB0aGlzIHByb2Nlc3MgdHVybnMgZWFjaCBpbmRpdmlkdWFsIGdyaWQgY2VsbCBpbnRvIGEgY2VudHJvaWQgcG9pbnQgYW5kIHRoZW4gbWVhc3VyZXMgdGhlIGRpc3RhbmNlcyBiZXR3ZWVuIGVhY2ggdG8gZmluZCB0aGUgbmVhcmVzdCByaXNrIGZhY3RvciBwb2ludC4gCgpUaGlzIHNwYXRpYWwgYW5hbHlzaXMgaXMgY3J1Y2lhbCBmb3IgdW5kZXJzdGFuZGluZyB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGRpZmZlcmVudCB1cmJhbiBmZWF0dXJlcyBhbmQgdGhlaXIgcHJveGltaXR5IHRvIG9uZSBhbm90aGVyLiBCeSBjYWxjdWxhdGluZyB0aGUgZGlzdGFuY2VzIGJldHdlZW4gdmFyaWFibGVzIGxpa2UgdmFjYW50IGJ1aWxkaW5ncywgYWZmb3JkYWJsZSBob3VzaW5nLCBncmVlbiBzcGFjZXMsIGFuZCBidWlsZGluZyBkZW1vbGl0aW9ucywgaXQgcHJvdmlkZXMgaW5zaWdodHMgaW50byB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gYW5kIHBvdGVudGlhbCBpbnRlcmFjdGlvbnMgYW1vbmcgdGhlc2UgZmVhdHVyZXMuCgpgYGB7ciBrbm4sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQoKIyMgMS4yLiBWYWNhbnQgQnVpbGRpbmdzCgojIyMgTWFwcGluZyBuZWFyZXN0IGZlYXR1cmUKCm5lYXJlc3RfdmFjYW50QnVpbGRpbmcgPC0gc2Y6OnN0X25lYXJlc3RfZmVhdHVyZSh2YXJzX25ldCwgdmFjYW50QnVpbGRpbmcpCgojIyMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzCgp4IDwtIHJzZ2VvOjphc19yc2dlbyh2YXJzX25ldCkKeSA8LSByc2dlbzo6YXNfcnNnZW8odmFjYW50QnVpbGRpbmcpCgojIyMgQ2FsY3VsYXRpbmcgZGlzdGFuY2UKCnZhcnNfbmV0JGRpc3RfdmFjYW50QnVpbGRpbmcgPC0gcnNnZW86OmRpc3RhbmNlX2V1Y2xpZGVhbl9wYWlyd2lzZSh4LCB5W25lYXJlc3RfdmFjYW50QnVpbGRpbmddKQoKCiMjIDEuMy4gQWZmb3JkYWJsZSBIb3VzaW5nCgojIyMgTWFwcGluZyBuZWFyZXN0IGZlYXR1cmUKCm5lYXJlc3RfYWZmb3JkYWJsZUhvdXNpbmcgPC0gc2Y6OnN0X25lYXJlc3RfZmVhdHVyZSh2YXJzX25ldCwgYWZmb3JkYWJsZUhvdXNpbmcpCgojIyMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzCgp4IDwtIHJzZ2VvOjphc19yc2dlbyh2YXJzX25ldCkKeSA8LSByc2dlbzo6YXNfcnNnZW8oYWZmb3JkYWJsZUhvdXNpbmcpCgojIyMgQ2FsY3VsYXRpbmcgZGlzdGFuY2UKCnZhcnNfbmV0JGRpc3RfYWZmb3JkYWJsZUhvdXNpbmcgPC0gcnNnZW86OmRpc3RhbmNlX2V1Y2xpZGVhbl9wYWlyd2lzZSh4LCB5W25lYXJlc3RfYWZmb3JkYWJsZUhvdXNpbmddKQoKCiMjIDEuNC4gR3JlZW4gU3BhY2VzCgojIyMgTWFwcGluZyBuZWFyZXN0IGZlYXR1cmUKCm5lYXJlc3RfZ3JlZW5TcGFjZSA8LSBzZjo6c3RfbmVhcmVzdF9mZWF0dXJlKHZhcnNfbmV0LCBncmVlblNwYWNlKQoKIyMjIENvbnZlcnRpbmcgdG8gcnNnZW8gZ2VvbWV0cmllcwoKeCA8LSByc2dlbzo6YXNfcnNnZW8odmFyc19uZXQpCnkgPC0gcnNnZW86OmFzX3JzZ2VvKGdyZWVuU3BhY2UpCgojIyMgQ2FsY3VsYXRpbmcgZGlzdGFuY2UKCnZhcnNfbmV0JGRpc3RfZ3JlZW5TcGFjZSA8LSByc2dlbzo6ZGlzdGFuY2VfZXVjbGlkZWFuX3BhaXJ3aXNlKHgsIHlbbmVhcmVzdF9ncmVlblNwYWNlXSkKCiMjIDEuOC4gQnVpbGRpbmcgRGVtb2xpdGlvbnMKCiMjIyBNYXBwaW5nIG5lYXJlc3QgZmVhdHVyZQoKbmVhcmVzdF9idWlsZGluZ0RlbW9saXRpb24gPC0gc2Y6OnN0X25lYXJlc3RfZmVhdHVyZSh2YXJzX25ldCwgYnVpbGRpbmdEZW1vbGl0aW9uKQoKIyMjIENvbnZlcnRpbmcgdG8gcnNnZW8gZ2VvbWV0cmllcwoKeCA8LSByc2dlbzo6YXNfcnNnZW8odmFyc19uZXQpCnkgPC0gcnNnZW86OmFzX3JzZ2VvKGJ1aWxkaW5nRGVtb2xpdGlvbikKCiMjIyBDYWxjdWxhdGluZyBkaXN0YW5jZQoKdmFyc19uZXQkZGlzdF9idWlsZGluZ0RlbW9saXRpb24gPC0gcnNnZW86OmRpc3RhbmNlX2V1Y2xpZGVhbl9wYWlyd2lzZSh4LCB5W25lYXJlc3RfYnVpbGRpbmdEZW1vbGl0aW9uXSkKCgpgYGAKCgpgYGB7ciB2aXpOTiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMgMi4gVmlzdWFsaXppbmcgbmVhcmVzdCBkaXN0YW5jZSBmb3Igc3BhdGlhbCBmYWN0b3JzIG9uIEZpc2huZXQKCiMjIDIuMS4gVmlzdWFsaXppbmcgdGhlIG5lYXJlc3QgdGhyZWUgZmVhdHVyZXMKCnZhcnNfbmV0Lmxvbmcubm4gPC0gCiAgZHBseXI6OnNlbGVjdCh2YXJzX25ldCwgc3RhcnRzX3dpdGgoImRpc3QiKSkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCB2YWx1ZSwgLWdlb21ldHJ5KQoKdmFycyA8LSB1bmlxdWUodmFyc19uZXQubG9uZy5ubiRWYXJpYWJsZSkKbWFwTGlzdCA8LSBsaXN0KCkKCmZvcihpIGluIHZhcnMpewogIG1hcExpc3RbW2ldXSA8LSAKICAgIGdncGxvdCgpICsKICAgIGdlb21fc2YoZGF0YSA9IGZpbHRlcih2YXJzX25ldC5sb25nLm5uLCBWYXJpYWJsZSA9PSBpKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiLAogICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICIgIikgKwogICAgbGFicyh0aXRsZT1pKSArCiAgICBtYXBUaGVtZSgpKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGNvbG9yID0gImJsYWNrIikpCiAgfQoKYm90dG9tQ2FwdGlvbiA8LSB0ZXh0R3JvYigiRmlndXJlIDgiLCBncCA9IGdwYXIoaGp1c3QgPSAwKSkKCmRvLmNhbGwoZ3JpZC5hcnJhbmdlLCBjKGxpc3QoZ3JvYnMgPSBtYXBMaXN0LCBuY29sID0gMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9wID0gdGV4dEdyb2IoIlNwYXRpYWwgRmFjdG9yczogTmVhcmVzdCBOZWlnaGJvciBEaXN0YW5jZSBmb3IgUGVybWl0cyBJc3N1ZWRcbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdwID0gZ3Bhcihmb250c2l6ZSA9IDE1LCBmb250ZmFjZSA9ICJib2xkIiwgY29sID0gImRhcmtyZWQiKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvdHRvbSA9IGJvdHRvbUNhcHRpb24pKSkKCmBgYAoKIyMjIFNwYXRpYWwgSm9pbnMKCkhlcmUgd2Ugd2FudCB0byBqb2luIHRoZSBjZW5zdXMgZGF0YSB0byBvdXIgZmlzaG5ldCBzbyB3ZSBjYW4gYWxzbyBtYXAgb3VyIGRlbW9ncmFwaGljIHZhcmlhYmxlcyBpbiBhIHNpbWlsYXIgZmFzaGlvbiwgYW5kIGFsc28ganVzdCBzdGFuZGFyZGl6ZSBvdXIgbW9kZSBvZiBhbmFseXNpcyBhY3Jvc3MgYWxsIG91ciBkYXRhLgoKYGBge3Igam9pbiBjZW5zdXMgZGF0YSB0byBmaXNobmV0LCByZXN1bHRzID0gJ2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyBKb2luaW5nIENlbnN1cyBEYXRhIHRvIEZpc2huZXQKCnRyYWN0czIyIDwtIHRyYWN0czIyICU+JQogIGZpbHRlcih0b3RhbFBvcD4wKQoKdmFyc19uZXQgPC0KICB2YXJzX25ldCU+JQogIHN0X2NlbnRyb2lkKCklPiUKICBzdF9qb2luKHRyYWN0czIyKQoKCnZhcnNfbmV0IDwtIHZhcnNfbmV0ICU+JSBtdXRhdGVfYWxsKH5yZXBsYWNlKC4sIGlzLm5hKC4pLCAwKSkKCiMgUGVyZm9ybSBTcGF0aWFsIEpvaW4gb2YgdmFyaWFibGVzIHdpdGggcGVybWl0cwoKZmluYWxfbmV0IDwtCiAgbGVmdF9qb2luKGNvbnN0cnVjdGlvbl9uZXQsIHN0X2Ryb3BfZ2VvbWV0cnkodmFyc19uZXQpLCBieT0idW5pcXVlSUQiKSAjIHRoaXMgb25lIGRvZXNuJ3Qgd29yayBzbyB0aGUgbGFzdCBvbmUgd29uJ3QgZWl0aGVyCgojIEZpbmFsIE5ldAoKCmZpbmFsX25ldCA8LQogIHN0X2NlbnRyb2lkKGZpbmFsX25ldCkgJT4lIAogICAgc3Rfam9pbihkcGx5cjo6c2VsZWN0KHBoaWxseU5laWdoYm9yaG9vZHMsIG5hbWUpLCBieSA9ICJ1bmlxdWVJRCIpICU+JSAKICAgICAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogICAgICBsZWZ0X2pvaW4oZHBseXI6OnNlbGVjdChmaW5hbF9uZXQsIGdlb21ldHJ5LCB1bmlxdWVJRCkpICU+JSAgCiAgICAgIHN0X3NmKCkgJT4lCiAgbmEub21pdCgpCmBgYAoKCmBgYHtyIGZpbmFsIG5ldCwgcmVzdWx0cyA9ICdoaWRlJywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiMjIGdlbmVyYXRlcyB3YXJuaW5ncyBmcm9tIFBST0ogaXNzdWVzCiMjIHtzcGRlcH0gdG8gbWFrZSBwb2x5Z29uIHRvIG5laWdoYm9yaG9vZHMuLi4gCmZpbmFsX25ldC5uYiA8LSBwb2x5Mm5iKGFzX1NwYXRpYWwoZmluYWxfbmV0KSwgcXVlZW49VFJVRSkKIyMgLi4uIGFuZCBuZWlnaGJvcmhvb2RzIHRvIGxpc3Qgb2Ygd2VpZ3RocwpmaW5hbF9uZXQud2VpZ2h0cyA8LSBuYjJsaXN0dyhmaW5hbF9uZXQubmIsIHN0eWxlPSJXIiwgemVyby5wb2xpY3k9VFJVRSkKCiMgcHJpbnQoZmluYWxfbmV0LndlaWdodHMsIHplcm8ucG9saWN5PVRSVUUpCmBgYAoKIyMgTW9kZWxpbmcgUHJvY2VzcwoKIyMjIExvY2FsIE1vcmFuJ3MgSQoKSW5jb3Jwb3JhdGluZyBMb2NhbCBNb3JhbidzIEkgYW5hbHlzaXMgaW50byB0aGUgc3BhdGlhbCBtb2RlbGluZyBwcm9jZXNzIGlzIHBpdm90YWwgZm9yIHVuZGVyc3RhbmRpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjb25zdHJ1Y3Rpb24gcGVybWl0cyBhY3Jvc3MgdXJiYW4gYXJlYXMuIExvY2FsIE1vcmFuJ3MgSSBzZXJ2ZXMgYXMgYSB2aXRhbCB0b29sLCBwcm92aWRpbmcgaW5zaWdodHMgaW50byB0aGUgc3BhdGlhbCBjbHVzdGVyaW5nIG9yIGRpc3BlcnNpb24gb2YgcGVybWl0IGlzc3VhbmNlLCB0aGVyZWJ5IGVuaGFuY2luZyB0aGUgbW9kZWwncyBjYXBhY2l0eSB0byBpZGVudGlmeSBzaWduaWZpY2FudCBzcGF0aWFsIHBhdHRlcm5zIGluIHVyYmFuIGRldmVsb3BtZW50LiBCeSBpbnRlZ3JhdGluZyBMb2NhbCBNb3JhbidzIEkgc3RhdGlzdGljcyB3aXRoIG90aGVyIHNwYXRpYWwgZmFjdG9ycywgc3VjaCBhcyBuZWFyZXN0IG5laWdoYm9yIGRpc3RhbmNlcyBhbmQgZ2VvZ3JhcGhpYyBmZWF0dXJlcywgdGhlIG1vZGVsIGdhaW5zIGEgY29tcHJlaGVuc2l2ZSB1bmRlcnN0YW5kaW5nIG9mIHRoZSB1bmRlcmx5aW5nIHNwYXRpYWwgZHluYW1pY3MgaW5mbHVlbmNpbmcgY29uc3RydWN0aW9uIHBlcm1pdCBkaXN0cmlidXRpb25zLCB0aGVyZWJ5IGZhY2lsaXRhdGluZyBpbmZvcm1lZCB1cmJhbiBwbGFubmluZyBhbmQgcG9saWN5IGRlY2lzaW9ucy5JbiB0aGUgY29udGV4dCBvZiB0aGlzIG1vZGVsLCBMb2NhbCBNb3JhbidzIEkgaGVscHMgaWRlbnRpZnkgc2lnbmlmaWNhbnQgc3BhdGlhbCBwYXR0ZXJucyBpbiBjb25zdHJ1Y3Rpb24gcGVybWl0IGlzc3VhbmNlLCBpbmRpY2F0aW5nIGFyZWFzIHdpdGggaGlnaCBvciBsb3cgY29uY2VudHJhdGlvbnMgb2YgcGVybWl0cy4gCgpJbml0aWFsbHksIHRoZSBsb2NhbCBNb3JhbidzIEkgdmFsdWVzIGFyZSBjb21wdXRlZCBmb3IgdGhlIHBlcm1pdCBjb3VudHMgdXNpbmcgc3BhdGlhbCB3ZWlnaHRzLiBUaGVuLCB0aGVzZSByZXN1bHRzIGFyZSBqb2luZWQgd2l0aCB0aGUgc3BhdGlhbCBkYXRhLCBhbmQgc2lnbmlmaWNhbnQgaG90c3BvdHMgYXJlIGlkZW50aWZpZWQgYmFzZWQgb24gYSBzaWduaWZpY2FuY2UgdGhyZXNob2xkIG9mIDAuMDUuIFRoZSBvdXRwdXQgaXMgb3JnYW5pemVkIGludG8gYSBsb25nIGZvcm1hdCBmb3IgcGxvdHRpbmcsIHdoZXJlIGVhY2ggdmFyaWFibGUncyBMb2NhbCBNb3JhbidzIEkgc3RhdGlzdGljcyBhcmUgdmlzdWFsaXplZCBhcyBzcGF0aWFsIG1hcHMuIFRoZXNlIG1hcHMgZGVwaWN0IHRoZSBzcGF0aWFsIGNsdXN0ZXJpbmcgcGF0dGVybnMgb2YgcGVybWl0IGlzc3VhbmNlLCBpbmRpY2F0aW5nIGFyZWFzIG9mIHNpZ25pZmljYW50IGNsdXN0ZXJpbmcgb3IgZGlzcGVyc2lvbi4gCgpgYGB7ciBsb2NhbCBtb3JhbiwgcmVzdWx0cyA9ICdoaWRlJywgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CiMjIHNlZSA/bG9jYWxtb3Jhbgpsb2NhbF9tb3JhbnMgPC0gbG9jYWxtb3JhbihmaW5hbF9uZXQkY291bnRQZXJtaXRzLCBmaW5hbF9uZXQud2VpZ2h0cywgemVyby5wb2xpY3k9VFJVRSkgJT4lIAogIGFzLmRhdGEuZnJhbWUoKQoKIyBqb2luIGxvY2FsIE1vcmFuJ3MgSSByZXN1bHRzIHRvIGZpc2huZXQKZmluYWxfbmV0LmxvY2FsTW9yYW5zIDwtIAogIGNiaW5kKGxvY2FsX21vcmFucywgYXMuZGF0YS5mcmFtZShmaW5hbF9uZXQpKSAlPiUgCiAgc3Rfc2YoKSAlPiUKICBkcGx5cjo6c2VsZWN0KFBlcm1pdF9Db3VudCA9IGNvdW50UGVybWl0cywgCiAgICAgICAgICAgICAgICBMb2NhbF9Nb3JhbnNfSSA9IElpLCAKICAgICAgICAgICAgICAgIFBfVmFsdWUgPSBgUHIoeiAhPSBFKElpKSlgKSAlPiUKICBtdXRhdGUoU2lnbmlmaWNhbnRfSG90c3BvdHMgPSBpZmVsc2UoKFBfVmFsdWUgPD0gMC4wNSksIDEsIDApKSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtZ2VvbWV0cnkpCiAgCmBgYAoKCmBgYHtyIGxvY2FsIG1vcmFucyBpLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NCwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CgojIyBUaGlzIGlzIGp1c3QgZm9yIHBsb3R0aW5nCnZhcnMgPC0gdW5pcXVlKGZpbmFsX25ldC5sb2NhbE1vcmFucyRWYXJpYWJsZSkKdmFyTGlzdCA8LSBsaXN0KCkKCmZvcihpIGluIHZhcnMpewogIHZhckxpc3RbW2ldXSA8LSAKICAgIGdncGxvdCgpICsKICAgICAgZ2VvbV9zZihkYXRhID0gZmlsdGVyKGZpbmFsX25ldC5sb2NhbE1vcmFucywgVmFyaWFibGUgPT0gaSksIAogICAgICAgICAgICAgIGFlcyhmaWxsID0gVmFsdWUpLCBjb2xvdXI9TkEpICsKICAgICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWU9IiIpICsKICAgICAgbGFicyh0aXRsZT1pKSArCiAgICAgIHRoZW1lX3ZvaWQoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIil9Cgpkby5jYWxsKGdyaWQuYXJyYW5nZSxjKHZhckxpc3QsIG5jb2wgPSA0LCB0b3AgPSAiTG9jYWwgTW9yYW5zIEkgU3RhdGlzdGljcywgQ29uc3RydWN0aW9uIFBlcm1pdHMiKSkKc2F2ZVJEUyhsb2NhbF9tb3JhbnMsIGZpbGUgPSAibG9jYWxfbW9yYW5zLnJkcyIpCmBgYAoKIyMjIERpc3RhbmNlIHRvIEhvdHNwb3RzCgpTaG9ydGVyIGRpc3RhbmNlcyBiZXR3ZWVuIHBlcm1pdHMgaW5kaWNhdGUgYXJlYXMgd2l0aCBoaWdoZXIgY29uc3RydWN0aW9uIGRlbnNpdHksIHdoaWNoIGNhbiBiZSBhc3NvY2lhdGVkIHdpdGggcmFwaWQgdXJiYW4gZGV2ZWxvcG1lbnQgb3IgZ2VudHJpZmljYXRpb24uCgpgYGB7ciBsbWlfaG90c3BvdCxyZXN1bHRzID0gJ2hpZGUnLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KCmxvY2FsX21vcmFucyA8LSByZWFkUkRTKCJsb2NhbF9tb3JhbnMucmRzIikKCmZpbmFsX25ldCA8LSBmaW5hbF9uZXQgJT4lCiAgbXV0YXRlKHBlcm1pdC5pc1NpZyA9IGlmZWxzZShsb2NhbF9tb3JhbnNbLDVdIDw9IDAuMDAwMDAwMSwxLDApKSAlPiUKICBtdXRhdGUocGVybWl0LmlzU2lnLmRpc3QgPSBubl9mdW5jdGlvbihzdF9jb29yZGluYXRlcyhzdF9jZW50cm9pZChmaW5hbF9uZXQpKSwgc3RfY29vcmRpbmF0ZXMoc3RfY2VudHJvaWQoZmlsdGVyKGZpbmFsX25ldCwgcGVybWl0LmlzU2lnID09IDEpKSksIDEpKQoKI3Bsb3R0aW5nIE5OIGhvdHNwb3RzCgpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gZmluYWxfbmV0LCBhZXMoZmlsbD1wZXJtaXQuaXNTaWcuZGlzdCksIGNvbG91ciA9IE5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWU9Ik5OIERpc3RhbmNlIikgKwogICAgICBsYWJzKHRpdGxlPSJQZXJtaXQgTk4gRGlzdGFuY2UiLCBjYXB0aW9uPSAiRmlndXJlIHh4IikgKwogICAgICBtYXBUaGVtZSgpKwogICAgICBwbG90VGhlbWUoKQoKI2xtb3JhbiA8LSBsb2NhbG1vcmFuKGZpbmFsX25ldCRsaWdodHNvdXQsIGZpbmFsX25ldC53ZWlnaHRzLCAgemVyby5wb2xpY3k9VFJVRSkKCiNmaW5hbF9uZXQkbG1JIDwtIGxtb3JhblssICJJaSJdICMgbG9jYWwgTW9yYW4ncyBJCiNmaW5hbF9uZXQkbG1aIDwtIGxtb3JhblssICJaLklpIl0gIyB6LXNjb3JlcwojZmluYWxfbmV0JGxtcCA8LSBsbW9yYW5bLCAiUHIoeiAhPSBFKElpKSkiXQoKCiNtcCA8LSBtb3Jhbi5wbG90KGFzLnZlY3RvcihzY2FsZShmaW5hbF9uZXQkbGlnaHRzb3V0KSksIGZpbmFsX25ldC53ZWlnaHRzLCB6ZXJvLnBvbGljeSA9IFRSVUUpCgojI0NyZWF0ZSBhIGhvdHNwb3QgdmFyaWFibGU6CiNmaW5hbF9uZXQkbG1wIDwtIGlmZWxzZShpcy5uYW4oZmluYWxfbmV0JGxtcCksIDAuMTAsIGZpbmFsX25ldCRsbXApCiNmaW5hbF9uZXQkaG90c3BvdCA8LSAwCiNmaW5hbF9uZXRbKG1wJHggPj0wICYgbXAkd3ggPj0wKSAmIGZpbmFsX25ldCRsbXAgPD0gMC4wNSwgImhvdHNwb3QiXTwtIDEKYGBgCgpgYGB7ciBob3RzcG90LCByZXN1bHRzID0gJ2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyBnZW5lcmF0ZXMgd2FybmluZyBmcm9tIE5OCiNmaW5hbF9uZXQgPC0gZmluYWxfbmV0ICU+JSAKICAjbXV0YXRlKGxpZ2h0c291dC5pc1NpZy5kaXN0ID0gCiAgICAgICAgICAgI25uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZChmaW5hbF9uZXQpKSwKICAgICAgICAgICAgICAgICAgICAgICAjc3RfYyhzdF9jb2lkKGZpbHRlcihmaW5hbF9uZXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgICAgIGhvdHNwb3QgPT0gMSkpKSwgCiMgICAgICAgICAgICAgICAgICAgICAgIGsgPSAxKSkKCmBgYAoKCmBgYHtyIHBsb3R0eSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CiNnZ3Bsb3QoKSArCiMgICAgICBnZW9tX3NmKGRhdGEgPSBmaW5hbF9uZXQsIGFlcyhmaWxsPWxpZ2h0c291dC5pc1NpZy5kaXN0KSwgY29sb3VyPU5BKSArCiMgICAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZT0iTk4gRGlzdGFuY2UiKSArCiMgICAgICBsYWJzKHRpdGxlPSJBbGxleSBMaWdodHMgT3V0IE5OIERpc3RhbmNlIikgKwojICAgICAgdGhlbWVfdm9pZCgpCmBgYAoKIyMgQnVpbGRpbmcgYW5kIGV2YWx1YXRpbmcgdGhlIG1vZGVsCgpUaGUgbW9kZWwgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGlzIGEgcG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsLiBUaGUgY29sbGVjdGVkIGFuZCBjbGVhbmVkIGRhdGEgdGhhdCB3YXMgdXNlZCBpbiB0aGlzIG1vZGVsIHdhcyBkZW1vZ3JhcGhpYyBpbmZvcm1hdGlvbiBsaWtlIHJhY2UsIGluY29tZSwgYW5kIHdvcmsgc3RhdHVzOyBob3VzaW5nIGluZm9ybWF0aW9uIGxpa2UgcGVyY2VudCByZW50ZXIgYW5kIG93bmVyIG9jY3VwaWVkOyByZWdpb25hbCBjaGFyYWN0ZXJpc3RpY3MgbGlrZSB2YWNhbnQgYnVpbGRpbmdzLCBjb25zdHJ1Y3Rpb24gcGVybWl0cywgYWZmb3JkYWJsZSBob3VzaW5nLCBhbmQgZGVtb2xpdGlvbnMuCgojIyMgQ29ycmVsYXRpb24KCmBgYHtyIC0gY29ycmVsYXRpb24gdGVzdHMgZm9yIHNwYXRpYWwgYW5kIGRlbW9ncmFwaGljIGZhY3RvcnMsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQoKY29ycmVsYXRpb24ubG9uZyA8LQogIHN0X2Ryb3BfZ2VvbWV0cnkoZmluYWxfbmV0KSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoLXVuaXF1ZUlELCAtY3ZJRCwgLW5hbWUpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWNvdW50UGVybWl0cykgJT4lCiAgbXV0YXRlKFZhbHVlID0gYXMubnVtZXJpYyhWYWx1ZSkpCgpjb3JyZWxhdGlvbi5jb3IgPC0KICBjb3JyZWxhdGlvbi5sb25nICU+JQogICAgZ3JvdXBfYnkoVmFyaWFibGUpICU+JQogICAgc3VtbWFyaXplKGNvcnJlbGF0aW9uID0gY29yKFZhbHVlLCBjb3VudFBlcm1pdHMsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKSkKCiMgVmlzdWFsaXppbmcgY29ycmVsYXRpb25zIHRocm91Z2ggc2NhdHRlciBwbG90cwojd2UgaGF2ZSBhIGxvdCBvZiBkZW1vZ3JhcGhpYyB2YXJpYWJsZXMgaGVyZSB0aGF0IGkgZG9uJ3Qga25vdyBpZiB3ZSBuZWNlc3NhcmlseSBuZWVkIG9yIGFyZSBpbnRlcmVzdGVkIGluIGtlZXBpbmcgZm9yIG91ciBmaW5hbCBzdHVmZiAtIHRoaW5rIGl0IG1heSBiZSBiZXR0ZXIgdG8gcGljayBhbmQgY2hvb3NlIGZld2VyIGRlbW8gdmFyaWFibGVzIGFuZCBtYXliZSBjaG9vc2UgbW9yZSBleHRlcm5hbCB2YXJpYWJsZXMKICAgIApnZ3Bsb3QoY29ycmVsYXRpb24ubG9uZywgYWVzKFZhbHVlLCBjb3VudFBlcm1pdHMpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC4xKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBjb3JyZWxhdGlvbi5jb3IsIGFlcyhsYWJlbCA9IHBhc3RlKCJyID0iLCByb3VuZChjb3JyZWxhdGlvbiwgMikpKSwKICAgICAgICAgICAgeD0tSW5mLCB5PUluZiwgdmp1c3QgPSAxLjUsIGhqdXN0ID0gLS4xKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3VyID0gIm9yYW5nZSIpICsKICBmYWNldF93cmFwKH5WYXJpYWJsZSwgbmNvbCA9IDQsIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnModGl0bGUgPSAiUGVybWl0IENvdW50IGFzIGEgZnVuY3Rpb24gb2YgcmlzayBmYWN0b3JzIiwgY2FwdGlvbj0iRmlndXJlIDEyIikgKwogIHBsb3RUaGVtZSgpCgpgYGAKCgpgYGB7ciAtIGNvcnJlbGF0aW9uIG1hdHJpeCwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CgpudW12YXJzIDwtIGMoImNvdW50UGVybWl0cyIsICJkaXN0X3ZhY2FudEJ1aWxkaW5nIiwgICJkaXN0X2FmZm9yZGFibGVIb3VzaW5nIiwiZGlzdF9ncmVlblNwYWNlIiwgImRpc3RfYnVpbGRpbmdEZW1vbGl0aW9uIiwgInRvdGFsUG9wIiwgIm1lZEhISW5jIiwgInBjdFdoaXRlIiwgInBjdEJsYWNrIiwgInBjdEJhY2giICwicGN0UG92IiwgInBjdFVuZW1wbG95IiwgInBjdE93bmVyT2NjdXBpZWQiLCAicGN0UmVudGVyT2NjdXBpZWQiKQoKbnVtZXJpYyA8LSBmaW5hbF9uZXQgJT4lCiAgc3RfZHJvcF9nZW9tZXRyeShmaW5hbF9uZXQpICU+JQogIGRwbHlyOjpzZWxlY3QobnVtdmFycyklPiUKICBuYS5vbWl0KCkKCmdnY29ycnBsb3QoCiAgcm91bmQoY29yKG51bWVyaWMpLCAxKSwgCiAgcC5tYXQgPSBjb3JfcG1hdChudW1lcmljKSwKICBjb2xvcnMgPSBjKCcjZDcxOTFjJywnd2hpdGUnLCcjMmM3YmI2JyksCiAgdHlwZT0ibG93ZXIiLAogIGluc2lnID0gImJsYW5rIikgKyAgCiAgICBsYWJzKHRpdGxlID0gIkNvcnJlbGF0aW9uIGFjcm9zcyBWYXJpYWJsZXNcbiIsIGNhcHRpb249IkZpZ3VyZSAxMyIpKyAKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExLCBmYWNlID0gImJvbGQiLCBjb2xvciA9ICJkYXJrcmVkIikpKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCkpKwogICAgdGhlbWUoYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCkpCgpgYGAKVGhlcmUgc2VlbXMgdG8gYmUgcmVsYXRpdmVseSBoaWdoIHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUgYW5kIHRoZSBwZXJjZW50IG9mIHRoZSBwb3B1bGF0aW9uIHRoYXQgaXMgd2hpdGUuIFRoaXMgbWF5IHN1Z2dlc3QgdGhhdCBhcmVhcyB3aXRoIG1vcmUgd2hpdGUgcmVzaWRlbnRzIGFyZSBoaWdoZXIgZWFybmluZyBuZWlnaGJvcmhvb2RzLCBvciBhIG5laWdoYm9yaG9vZCB0aGF0IGlzIGdlbnRyaWZ5aW5nIG1heSBoYXZlIG1vcmUgaGlnaGVyIGluY29tZSBhbmQgbW9yZSB3aGl0ZSByZXNpZGVudHMuIFRoZXJlIGlzIGEgcmVsYXRpdmVseSBoaWdoIG5lZ2F0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gdG90YWwgcG9wdWxhdGlvbiBhbmQgdGhlIGFtb3VudCBvZiB2YWNhbnQgYnVpbGRpbmdzLiBUaGlzIG1heSBtZWFuIHRoYXQgdGhlcmUgYXJlIGxlc3MgdmFjYW50IGJ1aWxkaW5ncyBpbiBtb3JlIHBvcHVsb3VzIGFyZWFzIC0gdGhpcyBjb3VsZCByZWxhdGUgdG8gZ2VudHJpZmljYXRpb24gaW4gdGhlIHdheSB0aGF0IG1vcmUgY3Jvd2RlZCwgY292ZXRlZCBuZWlnaGJvcmhvb2RzIHdpbGwgYmUgbW9yZSBwcmVzc2VkIGZvciBzcGFjZSwgbGVhZGluZyB0byByZWRldmVsb3BtZW50IG9mIHZhY2FudCBwcm9wZXJ0aWVzLgoKQ29uZHVjdGluZyBhIHJlZ3Jlc3Npb24gbW9kZWwgaW4gdGhpcyBjb250ZXh0IGlzIGNydWNpYWwgZm9yIHNldmVyYWwgcmVhc29ucy4gRmlyc3RseSwgaXQgYWxsb3dzIGZvciB0aGUgcXVhbnRpZmljYXRpb24gb2YgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHZhcmlvdXMgZmFjdG9ycyBhbmQgdGhlIG91dGNvbWUgb2YgaW50ZXJlc3QsIHN1Y2ggYXMgdGhlIG51bWJlciBvZiBwZXJtaXRzIGlzc3VlZCBvciB0aGUgbGlrZWxpaG9vZCBvZiBkZXZlbG9wbWVudC4gVGhpcyBlbmFibGVzIHRoZSBpZGVudGlmaWNhdGlvbiBvZiBzaWduaWZpY2FudCBwcmVkaWN0b3JzIGFuZCB0aGVpciByZXNwZWN0aXZlIGltcGFjdCBvbiB0aGUgb3V0Y29tZSwgYWlkaW5nIGluIHVuZGVyc3RhbmRpbmcgdGhlIHVuZGVybHlpbmcgZHluYW1pY3Mgb2YgZGV2ZWxvcG1lbnQgcmlzay4gQWRkaXRpb25hbGx5LCByZWdyZXNzaW9uIG1vZGVsaW5nIHByb3ZpZGVzIGEgbWVhbnMgdG8gYXNzZXNzIHRoZSByZWxhdGl2ZSBpbXBvcnRhbmNlIG9mIGRpZmZlcmVudCB2YXJpYWJsZXMsIHByaW9yaXRpemUgaW50ZXJ2ZW50aW9ucywgYW5kIGluZm9ybSBkZWNpc2lvbi1tYWtpbmcgcHJvY2Vzc2VzIGFpbWVkIGF0IG1pdGlnYXRpbmcgZGV2ZWxvcG1lbnQtcmVsYXRlZCBjaGFsbGVuZ2VzIG9yIHByb21vdGluZyBzdXN0YWluYWJsZSB1cmJhbiBncm93dGguCgpDcm9zcy12YWxpZGF0aW9uIGlzIHBlcmZvcm1lZCB0byBldmFsdWF0ZSB0aGUgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBvZiBQb2lzc29uIHJlZ3Jlc3Npb24gbW9kZWxzIGFwcGxpZWQgdG8gdGhlIGRhdGFzZXQuIFRoaXMgcHJvY2VzcyBpbnZvbHZlcyBzcGxpdHRpbmcgdGhlIGRhdGFzZXQgaW50byBkaXN0aW5jdCBmb2xkcywgd2hlcmUgZWFjaCBmb2xkIHNlcnZlcyBhcyBhIHRyYWluaW5nIHNldCBmb3IgbW9kZWwgZGV2ZWxvcG1lbnQgYW5kIGEgdGVzdGluZyBzZXQgZm9yIHBlcmZvcm1hbmNlIGFzc2Vzc21lbnQuIFdpdGhpbiBlYWNoIGl0ZXJhdGlvbiwgYSBQb2lzc29uIHJlZ3Jlc3Npb24gbW9kZWwgaXMgdHJhaW5lZCB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgdGhlbiBhcHBsaWVkIHRvIHRoZSB0ZXN0aW5nIGRhdGEgdG8gbWFrZSBwcmVkaWN0aW9ucy4gVGhlc2UgcHJlZGljdGlvbnMgYXJlIGNvbXBhcmVkIGFnYWluc3QgdGhlIGFjdHVhbCB2YWx1ZXMgdG8gYXNzZXNzIHRoZSBtb2RlbCdzIGFjY3VyYWN5IGFuZCByb2J1c3RuZXNzLiBUaGUgY3Jvc3MtdmFsaWRhdGlvbiBwcm9jZWR1cmUgaGVscHMgZW5zdXJlIHRoYXQgdGhlIG1vZGVscyBnZW5lcmFsaXplIHdlbGwgdG8gdW5zZWVuIGRhdGEgYW5kIHByb3ZpZGUgcmVsaWFibGUgcHJlZGljdGlvbnMgaW4gcmVhbC13b3JsZCBzY2VuYXJpb3MsIGVuaGFuY2luZyB0aGUgb3ZlcmFsbCByZWxpYWJpbGl0eSBvZiB0aGUgYW5hbHlzaXMuCgpUaGUgcHJvdmlkZWQgY29kZSBpbXBsZW1lbnRzIGEgY3Jvc3MtdmFsaWRhdGlvbiBwcm9jZWR1cmUgZm9yIGV2YWx1YXRpbmcgUG9pc3NvbiByZWdyZXNzaW9uIG1vZGVscyBhcHBsaWVkIHRvIHRoZSBkYXRhc2V0IGBmaW5hbF9uZXRgLiBJdCBkZWZpbmVzIGEgZnVuY3Rpb24gYGNyb3NzVmFsaWRhdGUoKWAgdG8gY29uZHVjdCBjcm9zcy12YWxpZGF0aW9uLCB3aGVyZSB0aGUgZGF0YXNldCBpcyBzcGxpdCBpbnRvIGRpc3RpbmN0IGZvbGRzIChgY3ZJRGApIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZy4gV2l0aGluIGVhY2ggZm9sZCwgYSBQb2lzc29uIHJlZ3Jlc3Npb24gbW9kZWwgaXMgdHJhaW5lZCB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSAoYGZvbGQudHJhaW5gKSBhbmQgdGhlbiBhcHBsaWVkIHRvIHRoZSB0ZXN0aW5nIGRhdGEgKGBmb2xkLnRlc3RgKSB0byBtYWtlIHByZWRpY3Rpb25zLiBUaGVzZSBwcmVkaWN0aW9ucyBhcmUgYWdncmVnYXRlZCBhY3Jvc3MgYWxsIGZvbGRzIHRvIGFzc2VzcyB0aGUgb3ZlcmFsbCBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG9mIHRoZSBtb2RlbC4gVGhlIGNyb3NzLXZhbGlkYXRpb24gaGVscHMgdmFsaWRhdGUgdGhlIG1vZGVsJ3MgYWJpbGl0eSB0byBnZW5lcmFsaXplIHRvIHVuc2VlbiBkYXRhLCBlbnN1cmluZyB0aGUgcmVsaWFiaWxpdHkgb2YgdGhlIHJlZ3Jlc3Npb24gYW5hbHlzaXMuCgojIyMgVmFyaWFibGUgU2VsZWN0aW9uCgpCYXNlZCBvbiB0aGUgY29ycmVsYXRpb24gYW5hbHlzaXMsIHZhcmlhYmxlcyBzdWNoIGFzICdWYWNhbnQgQnVpbGRpbmcgRGlzdGFuY2UnLCAnQWZmb3JkYWJsZSBIb3VzaW5nIERpc3RhbmNlJywgJ0dyZWVuIFNwYWNlIERpc3RhbmNlJywgJ0J1aWxkaW5nIERlbW9saXRpb24gRGlzdGFuY2UnLCAnTWVkaWFuIEhvdXNlaG9sZCBJbmNvbWUnLCAnUGVyY2VudCBCZWxvdyBQb3ZlcnR5IExpbmUnLCAnUGVyY2VudCBVbmVtcGxveWVkJywgYW5kICdQZXJjZW50IE93bmVyIE9jY3VwaWVkJyBhcmUgc2VsZWN0ZWQgYXMgbW9kZWwgdmFyaWFibGVzLiAKCmBgYHtyIHJlZ3Jlc3Npb24sIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KCiMjIGRlZmluZSB0aGUgdmFyaWFibGVzIHdlIHdhbnQKcmVnLnZhcnMgPC0gYygiZGlzdF92YWNhbnRCdWlsZGluZyIsICAiZGlzdF9hZmZvcmRhYmxlSG91c2luZyIsImRpc3RfZ3JlZW5TcGFjZSIsICJkaXN0X2J1aWxkaW5nRGVtb2xpdGlvbiIsICJtZWRISEluYyIsICJwY3RCYWNoIiAsInBjdFBvdiIsICJwY3RVbmVtcGxveSIsICJwY3RPd25lck9jY3VwaWVkIikKCnJlZy5zcy52YXJzIDwtIGMoImRpc3RfdmFjYW50QnVpbGRpbmciLCAgImRpc3RfYWZmb3JkYWJsZUhvdXNpbmciLCJkaXN0X2dyZWVuU3BhY2UiLCAiZGlzdF9idWlsZGluZ0RlbW9saXRpb24iLCAibWVkSEhJbmMiLCAicGN0QmFjaCIgLCJwY3RQb3YiLCAicGN0VW5lbXBsb3kiLCAicGN0T3duZXJPY2N1cGllZCIsICJwZXJtaXQuaXNTaWciKQoKIyMgY3JlYXRpbmcgZnVuY3Rpb25zIGZvciBjcm9zcyB2YWxpZGF0aW9uCgpjcm9zc1ZhbGlkYXRlIDwtIGZ1bmN0aW9uKGRhdGFzZXQsIGlkLCBkZXBlbmRlbnRWYXJpYWJsZSwgaW5kVmFyaWFibGVzKSB7CiAgCiAgYWxsUHJlZGljdGlvbnMgPC0gZGF0YS5mcmFtZSgpCiAgY3ZJRF9saXN0IDwtIHVuaXF1ZShkYXRhc2V0W1tpZF1dKQogIAogIGZvciAoaSBpbiBjdklEX2xpc3QpIHsKICAgIAogICAgdGhpc0ZvbGQgPC0gaQogICAgY2F0KCJUaGlzIGhvbGQgb3V0IGZvbGQgaXMiLCB0aGlzRm9sZCwgIlxuIikKICAgIAogICAgZm9sZC50cmFpbiA8LSBmaWx0ZXIoZGF0YXNldCwgZGF0YXNldFtbaWRdXSAhPSB0aGlzRm9sZCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgICAgIGRwbHlyOjpzZWxlY3QoaWQsIGdlb21ldHJ5LCBpbmRWYXJpYWJsZXMsIGRlcGVuZGVudFZhcmlhYmxlKQogICAgZm9sZC50ZXN0ICA8LSBmaWx0ZXIoZGF0YXNldCwgZGF0YXNldFtbaWRdXSA9PSB0aGlzRm9sZCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgICAgIGRwbHlyOjpzZWxlY3QoaWQsIGdlb21ldHJ5LCBpbmRWYXJpYWJsZXMsIGRlcGVuZGVudFZhcmlhYmxlKQogICAgCiAgICByZWdyZXNzaW9uIDwtCiAgICAgIGdsbShjb3VudFBlcm1pdHMgfiAuLCBmYW1pbHkgPSAicG9pc3NvbiIsIAogICAgICAgICAgZGF0YSA9IGZvbGQudHJhaW4gJT4lIAogICAgICAgICAgICBkcGx5cjo6c2VsZWN0KC1nZW9tZXRyeSwgLWlkKSkKICAgIAogICAgdGhpc1ByZWRpY3Rpb24gPC0gCiAgICAgIG11dGF0ZShmb2xkLnRlc3QsIFByZWRpY3Rpb24gPSBwcmVkaWN0KHJlZ3Jlc3Npb24sIGZvbGQudGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpKQogICAgCiAgICBhbGxQcmVkaWN0aW9ucyA8LQogICAgICByYmluZChhbGxQcmVkaWN0aW9ucywgdGhpc1ByZWRpY3Rpb24pCiAgICAKICB9CiAgcmV0dXJuKHN0X3NmKGFsbFByZWRpY3Rpb25zKSkKfQpgYGAKCiMjIyBDcm9zcyBWYWxpZGF0aW9uCgpUaGlzIHNob3dzIHRoZSBwcm9jZXNzIG9mIGNvbmR1Y3RpbmcgY3Jvc3MtdmFsaWRhdGlvbiBvbiBQb2lzc29uIHJlZ3Jlc3Npb24gbW9kZWxzIHVzaW5nIHRoZSBjcm9zc1ZhbGlkYXRlIGZ1bmN0aW9uLCBhaW1lZCBhdCBwcmVkaWN0aW5nIHRoZSBudW1iZXIgb2YgY29uc3RydWN0aW9uIHBlcm1pdHMgKGNvdW50UGVybWl0cykgaW4gUGhpbGFkZWxwaGlhIG5laWdoYm9yaG9vZHMuIERpZmZlcmVudCBtb2RlbHMgYXJlIGJlaW5nIGV2YWx1YXRlZDogYSBub24tc3BhdGlhbCBwcm9jZXNzIG1vZGVsIHVzaW5nIGEgZGVmaW5lZCBzZXQgb2YgdmFyaWFibGVzIChyZWcudmFycykgYW5kIGEgc3BhdGlhbCBwcm9jZXNzIG1vZGVsLCB3aGljaCBpbnRlZ3JhdGVzIHNwYXRpYWwgY2hhcmFjdGVyaXN0aWNzIGFsb25nIHdpdGggdGhlIHNhbWUgc2V0IG9mIHZhcmlhYmxlcyB0byB1bmRlcnN0YW5kIGhvdyBpbmNsdWRpbmcgc3BhdGlhbCBkYXRhIGluZmx1ZW5jZXMgdGhlIGFjY3VyYWN5IGFuZCBwcmVkaWN0aXZlbmVzcyBvZiB0aGUgbW9kZWwgb3V0Y29tZXMuIFRoaXMgY3Jvc3MtdmFsaWRhdGlvbiBhcHByb2FjaCBoZWxwcyBpbiBhc3Nlc3NpbmcgdGhlIG1vZGVsJ3Mgcm9idXN0bmVzcyBhbmQgZ2VuZXJhbGl6YWJpbGl0eSBieSB0ZXN0aW5nIGl0IG9uIG11bHRpcGxlIHN1YnNldHMgb2YgZGF0YS4KCmBgYHtyIGNyb3NzIHZhbGlkYXRpb24sIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KCiNjb25kdWN0aW5nIGNyb3NzIHZhbGlkYXRpb24gb24gUG9pc3NvbiByZWdyZXNzaW5vIG1vZGVscwoKcmVnLmN2IDwtIGNyb3NzVmFsaWRhdGUoCiAgZGF0YXNldCA9IGZpbmFsX25ldCwKICBpZCA9ICJjdklEIiwKICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJjb3VudFBlcm1pdHMiLAogIGluZFZhcmlhYmxlcyA9IHJlZy52YXJzKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoY3ZJRCA9IGN2SUQsIGNvdW50UGVybWl0cywgUHJlZGljdGlvbiwgZ2VvbWV0cnkpCgpyZWcuc3MuY3YgPC0gY3Jvc3NWYWxpZGF0ZSgKICBkYXRhc2V0ID0gZmluYWxfbmV0LAogIGlkID0gImN2SUQiLAogIGRlcGVuZGVudFZhcmlhYmxlID0gImNvdW50UGVybWl0cyIsCiAgaW5kVmFyaWFibGVzID0gcmVnLnNzLnZhcnMpICU+JQogICAgZHBseXI6OnNlbGVjdChjdklEID0gY3ZJRCwgY291bnRQZXJtaXRzLCBQcmVkaWN0aW9uLCBnZW9tZXRyeSkKCmZpbmFsX25ldCRuYW1lIDwtIGlmZWxzZShpcy5uYShmaW5hbF9uZXQkbmFtZSksICJVTktOT1dOIiwgZmluYWxfbmV0JG5hbWUpCiAgCnJlZy5zcGF0aWFsQ1YgPC0gY3Jvc3NWYWxpZGF0ZSgKICBkYXRhc2V0ID0gZmluYWxfbmV0LAogIGlkID0gIm5hbWUiLAogIGRlcGVuZGVudFZhcmlhYmxlID0gImNvdW50UGVybWl0cyIsCiAgaW5kVmFyaWFibGVzID0gcmVnLnZhcnMpICU+JQogICAgZHBseXI6OnNlbGVjdChjdklEID0gbmFtZSwgY291bnRQZXJtaXRzLCBQcmVkaWN0aW9uLCBnZW9tZXRyeSkKCnJlZy5zcy5zcGF0aWFsQ1YgPC0gY3Jvc3NWYWxpZGF0ZSgKICBkYXRhc2V0ID0gZmluYWxfbmV0LAogIGlkID0gIm5hbWUiLAogIGRlcGVuZGVudFZhcmlhYmxlID0gImNvdW50UGVybWl0cyIsCiAgaW5kVmFyaWFibGVzID0gcmVnLnNzLnZhcnMpICU+JQogICAgZHBseXI6OnNlbGVjdChjdklEID0gbmFtZSwgY291bnRQZXJtaXRzLCBQcmVkaWN0aW9uLCBnZW9tZXRyeSkKYGBgCgojIyMgRXJyb3IgQ2FsY3VsYXRpb24KCkluIGVhY2ggcmVncmVzc2lvbiwgdGhlIGFic29sdXRlIGVycm9yIGlzIGRldGVybWluZWQgYnkgY2FsY3VsYXRpbmcgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcHJlZGljdGVkIGFuZCB0aGUgYWN0dWFsIG9ic2VydmVkIGNvdW50cyBvZiBuZXcgY29uc3RydWN0aW9uIHBlcm1pdHMuCgpgYGB7ciByZWdyZXNzaW9uIGVycm9ycywgcmVzdWx0cz0naGlkZScsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIGNhbGN1bGF0ZSBlcnJvcnMKCnJlZy5zdW1tYXJ5IDwtIAogIHJiaW5kKAogICAgbXV0YXRlKHJlZy5jdiwgICAgICAgICAgIEVycm9yID0gUHJlZGljdGlvbiAtIGNvdW50UGVybWl0cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWdyZXNzaW9uID0gIlJhbmRvbSBrLWZvbGQgQ1Y6IEp1c3QgUmlzayBGYWN0b3JzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICBtdXRhdGUocmVnLnNzLmN2LCAgICAgICAgRXJyb3IgPSBQcmVkaWN0aW9uIC0gY291bnRQZXJtaXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZ3Jlc3Npb24gPSAiUmFuZG9tIGstZm9sZCBDVjogU3BhdGlhbCBQcm9jZXNzIiksCiAgICAKICAgIG11dGF0ZShyZWcuc3BhdGlhbENWLCAgICBFcnJvciA9IFByZWRpY3Rpb24gLSBjb3VudFBlcm1pdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVncmVzc2lvbiA9ICJTcGF0aWFsIExPR08tQ1Y6IEp1c3QgUmlzayBGYWN0b3JzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICBtdXRhdGUocmVnLnNzLnNwYXRpYWxDViwgRXJyb3IgPSBQcmVkaWN0aW9uIC0gY291bnRQZXJtaXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZ3Jlc3Npb24gPSAiU3BhdGlhbCBMT0dPLUNWOiBTcGF0aWFsIFByb2Nlc3MiKSkgJT4lCiAgICBzdF9zZigpCmBgYAoKCmBgYCB7ciBlcnJvcnMsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KCiMgQ2FsY3VsYXRlIE1BRSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIGZvciBlYWNoIGZvbGQgYW5kIG1ldGhvZAplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgPC0gCiAgcmVnLnN1bW1hcnkgJT4lIAogIGdyb3VwX2J5KFJlZ3Jlc3Npb24sIGN2SUQpICU+JSAKICBzdW1tYXJpemUoTWVhbl9FcnJvciA9IG1lYW4oUHJlZGljdGlvbiAtIGNvdW50UGVybWl0cywgbmEucm0gPSBUKSwKICAgIE1BRSA9IG1lYW4oYWJzKE1lYW5fRXJyb3IpLCBuYS5ybSA9IFRSVUUpLAogICAgU0RfTUFFID0gbWVhbihhYnMoTWVhbl9FcnJvciksIG5hLnJtID0gVFJVRSksCiAgICAuZ3JvdXBzID0gJ2Ryb3AnCiAgKQoKIyBBcnJhbmdlIGJ5IE1BRSBmb3Igdmlld2luZwplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgJT4lIAogIGFycmFuZ2UoZGVzYyhNQUUpKQplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgJT4lIAogIGFycmFuZ2UoTUFFKQoKIyBQbG90IGhpc3RvZ3JhbSBvZiBPT0YgZXJyb3JzIGZvciBlYWNoIG1ldGhvZAplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgJT4lCiAgZ2dwbG90KGFlcyh4ID0gTUFFKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzAsIGNvbG91cj0iYmxhY2siLCBmaWxsID0gIiNGREU3MjVGRiIpICsKICBmYWNldF93cmFwKH4gUmVncmVzc2lvbiwgc2NhbGVzID0gImZyZWUiKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAxMSwgYnkgPSAxKSkgKwogIGxhYnModGl0bGU9IkRpc3RyaWJ1dGlvbiBvZiBNQUUiLCBzdWJ0aXRsZSA9ICJSYW5kb20gSy1Gb2xkIGFuZCBMT0dPLUNWIiwKICAgICAgIHg9Ik1lYW4gQWJzb2x1dGUgRXJyb3IiLCB5PSJDb3VudCIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSwgZmFjZT0gImJvbGQiLCBjb2xvciA9ICJkYXJrcmVkIikpCmBgYAoKCgpgYGAge3Igc3BhdGlhbCBsb2dvIGN2IGVycm9ycywgcmVzdWx0cz0naGlkZScsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QoUmVncmVzc2lvbiwgIkxPR08iKSkgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHBobEJvdW5kYXJ5LCBmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiZGFya2dyZXkiKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gTUFFKSkgKwogICAgZmFjZXRfd3JhcCh+UmVncmVzc2lvbikgKwogICAgc2NhbGVfY29sb3VyX3ZpcmlkaXMob3B0aW9uID0gInBsYXNtYSIpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAicGxhc21hIikgKwogICAgbGFicyh0aXRsZSA9ICJFcnJvcnMgYnkgTE9HTy1DViBSZWdyZXNzaW9uIiwgY2FwdGlvbj0iRmlndXJlIDE1IikgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUsIGZhY2U9ICJib2xkIiwgY29sb3IgPSAiZGFya3JlZCIpKSsKICAgIHRoZW1lKHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKQoKYGBgCgoKYGBge3IgdGFibGVzLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyBUYWJsZSBvZiBNQUUgYW5kIFN0YW5kYXJkIERldmlhdGlvbiBNQUUKCnN0X2Ryb3BfZ2VvbWV0cnkoZXJyb3JfYnlfcmVnX2FuZF9mb2xkKSAlPiUKICBncm91cF9ieShSZWdyZXNzaW9uKSAlPiUgCiAgICBzdW1tYXJpemUoTWVhbl9NQUUgPSByb3VuZChtZWFuKE1BRSksIDIpLAogICAgICAgICAgICAgIFNEX01BRSA9IHJvdW5kKHNkKE1BRSksIDIpKSAlPiUKICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDE6IE1BRSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIE1BRSBieSByZWdyZXNzaW9uIikgJT4lCiAgICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYpICU+JQogICAgcm93X3NwZWMoMiwgY29sb3IgPSAiYmxhY2siLCBiYWNrZ3JvdW5kID0gIiNGREU3MjVGRiIpICU+JQogICAgcm93X3NwZWMoNCwgY29sb3IgPSAiYmxhY2siLCBiYWNrZ3JvdW5kID0gIiNGREU3MjVGRiIpIAoKYGBgClRoZSBhcmVhcyB3aXRoIHNvbWUgb2YgdGhlIGdyZWF0ZXN0IG1lYW4gYWJzb2x1dGUgZXJyb3IgYXJlIGNsb3NlciB0byBjZW50ZXIgY2l0eSwgc3VjaCBhcyBGaXNodG93biwgVW5pdmVyc2l0eSBDaXR5LCBhbmQgR3JhZHVhdGUgSG9zcGl0YWwuIFJlZ2lvbnMgb2YgUGhpbGFkZWxwaGlhIGNsb3NlIHRvIHRoZSBOb3J0aCwgV2VzdCwgU291dGgsIGFuZCBhbG9uZyB0aGUgRGVsYXdhcmUgcml2ZXIgc2hvd2VkIGxvd2VyIGVycm9ycywgYm90aCBpbiB0ZXJtcyBvZiBqdXN0IHJpc2sgZmFjdG9ycyBhbmQgc3BhdGlhbCBwcm9jZXNzLiBUaGVyZSB3YXMgaGlnaGVyIGVycm9yIHNob3duIGluIHRoZSBTcGF0aWFsIExPR08tQ1YgcHJvY2VzcyB0aGFuIHRoZSBvdGhlciB2YWxpZGF0aW9uIG1ldGhvZHMuIFRoZSByYW5kb20gay1mb2xkIHByb2Nlc3NlcyByZXN1bHRlZCBpbiBsb3dlciBtZWFuIGFic29sdXRlIGVycm9yLgoKYGBgIHtyIG1vcmFucyBpIGVycm9ycywgcmVzdWx0cz0naGlkZScsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIEFzc3VtaW5nICdlcnJvcl9ieV9yZWdfYW5kX2ZvbGQnIGNvbnRhaW5zIHRoZSBuZWNlc3NhcnkgY29sdW1ucyBhbmQgc3BhdGlhbCBkYXRhCiMgRmlyc3QsIGVuc3VyZSB0aGF0IHlvdXIgZGF0YSBmcmFtZSBoYXMgdGhlIGFwcHJvcHJpYXRlIHN0cnVjdHVyZSBhbmQgdW5pcXVlIHJvdyBuYW1lcwoKIyBDaGVjayBmb3IgdW5pcXVlIHJvdyBuYW1lcyBhbmQgcmVzZXQgaWYgbmVjZXNzYXJ5CmlmKGFueUR1cGxpY2F0ZWQocm93Lm5hbWVzKGVycm9yX2J5X3JlZ19hbmRfZm9sZCkpKSB7CiAgcm93bmFtZXMoZXJyb3JfYnlfcmVnX2FuZF9mb2xkKSA8LSBtYWtlLnVuaXF1ZShhcy5jaGFyYWN0ZXIocm93Lm5hbWVzKGVycm9yX2J5X3JlZ19hbmRfZm9sZCkpKQp9CgojIENyZWF0ZSB3ZWlnaHRzIG9ubHkgZm9yIHRoZSBzZWxlY3RlZCByZWdyZXNzaW9uIHR5cGUKbmVpZ2hib3Job29kLndlaWdodHMgPC0gZXJyb3JfYnlfcmVnX2FuZF9mb2xkICU+JQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJTcGF0aWFsIExPR08tQ1Y6IFNwYXRpYWwgUHJvY2VzcyIpICU+JQogIHN0X2FzX3NmKCkgJT4lCiAgZ3JvdXBfYnkoY3ZJRCkgJT4lCiAgcG9seTJuYiguLCBxdWVlbiA9IFRSVUUpICU+JSAgIyBDb3JyZWN0ZWQ6IFJlbW92ZWQgc3R5bGUgYW5kIHplcm8ucG9saWN5CiAgbmIybGlzdHcoLiwgc3R5bGUgPSAiVyIsIHplcm8ucG9saWN5ID0gVFJVRSkgICMgQ29ycmVjdCB1c2FnZSBvZiBzdHlsZSBhbmQgemVyby5wb2xpY3kKCmZpbHRlcihlcnJvcl9ieV9yZWdfYW5kX2ZvbGQsIHN0cl9kZXRlY3QoUmVncmVzc2lvbiwgIkxPR08iKSkgICU+JSAKICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICAgIGdyb3VwX2J5KFJlZ3Jlc3Npb24pICU+JQogICAgc3VtbWFyaXplKE1vcmFuc19JID0gbW9yYW4ubWMoYWJzKE1lYW5fRXJyb3IpLCBuZWlnaGJvcmhvb2Qud2VpZ2h0cywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5zaW0gPSA5OTksIHplcm8ucG9saWN5ID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbj1uYS5vbWl0KVtbMV1dLAogICAgICAgICAgICAgIHBfdmFsdWUgPSBtb3Jhbi5tYyhhYnMoTWVhbl9FcnJvciksIG5laWdoYm9yaG9vZC53ZWlnaHRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnNpbSA9IDk5OSwgemVyby5wb2xpY3kgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEuYWN0aW9uPW5hLm9taXQpW1szXV0pICU+JSAKICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDI6IE1vcmFuJ3MgSSBvbiBFcnJvcnMgYnkgUmVncmVzc2lvbiIpICU+JQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKSAlPiUKICAgIHJvd19zcGVjKDEsIGNvbG9yID0gImJsYWNrIiwgYmFja2dyb3VuZCA9ICIjRkRFNzI1RkYiKSAlPiUKICAgIHJvd19zcGVjKDEsIGNvbG9yID0gImJsYWNrIiwgYmFja2dyb3VuZCA9ICIjRkRFNzI1RkYiKSAKCgpgYGAKVGhpcyBkaXNwbGF5cyB0aGUgcmVzaWR1YWxzIG9mIGRpZmZlcmVudCByZWdyZXNzaW9uIG1vZGVscyB1c2VkIHRvIHByZWRpY3QgbmV3IGNvbnN0cnVjdGlvbiBwZXJtaXRzLCBzZWdtZW50ZWQgYnkgbW9kZWwgdHlwZS4gRnJvbSB0aGUgcGxvdCwgaXQncyBldmlkZW50IHRoYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiByZXNpZHVhbHMgdmFyaWVzIGFjcm9zcyB0aGUgbW9kZWxzLCB3aXRoIHNvbWUgc2hvd2luZyBhIHRpZ2h0ZXIgY2x1c3RlciBhcm91bmQgdGhlIHplcm8gbGluZSAoaW5kaWNhdGluZyBiZXR0ZXIgcHJlZGljdGlvbiBhY2N1cmFjeSkgYW5kIG90aGVycyBkaXNwbGF5aW5nIG1vcmUgc3ByZWFkLCBzdWdnZXN0aW5nIGxlc3MgcHJlY2lzaW9uLiAKCmBgYHtyIHJlc2lkdWFscywgcmVzdWx0cz0naGlkZScsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQoKcmVnLnN1bW1hcnkgPC0gcmVnLnN1bW1hcnkgJT4lCiAgbXV0YXRlKFJlc2lkdWFscyA9IGNvdW50UGVybWl0cyAtIFByZWRpY3Rpb24pCgojIHJlc2lkdWFsIHBsb3QKCmdncGxvdChyZWcuc3VtbWFyeSwgYWVzKHggPSBQcmVkaWN0aW9uLCB5ID0gUmVzaWR1YWxzKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjQpICsgICMgVXNlIGFscGhhIHRvIGFkanVzdCBwb2ludCB0cmFuc3BhcmVuY3kKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJyZWQiKSArICAjIEFkZCBhIGhvcml6b250YWwgbGluZSBhdCB5ID0gMAogIGxhYnModGl0bGUgPSAiUmVzaWR1YWwgUGxvdCIsIHggPSAiUHJlZGljdGVkIFZhbHVlcyIsIHkgPSAiUmVzaWR1YWxzIikgKwogIHRoZW1lX21pbmltYWwoKQoKCmdncGxvdChyZWcuc3VtbWFyeSwgYWVzKHggPSBQcmVkaWN0aW9uLCB5ID0gUmVzaWR1YWxzLCBjb2xvciA9IFJlZ3Jlc3Npb24pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsKICBmYWNldF93cmFwKH4gUmVncmVzc2lvbikgKyAgIyBTZXBhcmF0ZSBwbG90IGZvciBlYWNoIHJlZ3Jlc3Npb24gdHlwZQogIGxhYnModGl0bGUgPSAiUmVzaWR1YWwgUGxvdCBieSBSZWdyZXNzaW9uIFR5cGUiLCB4ID0gIlByZWRpY3RlZCBWYWx1ZXMiLCB5ID0gIlJlc2lkdWFscyIpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2QoKSArICAjIENvcnJlY3RlZCB0byB1c2UgZGlzY3JldGUgY29sb3Igc2NhbGUKICB0aGVtZV9taW5pbWFsKCkKCmBgYAoKIyMjIEtlcm5lbCBEZW5zaXR5CgpUaGUgc2VyaWVzIG9mIHBsb3RzIHByb3ZpZGVkIGlsbHVzdHJhdGUgdGhlIHNwYXRpYWwgZGlzdHJpYnV0aW9uIG9mIGNvbnN0cnVjdGlvbiBwZXJtaXRzIGluIFBoaWxhZGVscGhpYSB1c2luZyBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0ZXMgd2l0aCBkaWZmZXJlbnQgc2VhcmNoIHJhZGlpIGFuZCBhIGNvbXBhcmlzb24gb2YgdGhlc2UgZXN0aW1hdGVzIHdpdGggc3BhdGlhbCByaXNrIHByZWRpY3Rpb25zLiBUaGUgZmlyc3Qgc2V0IG9mIGltYWdlcyBzaG93cyBrZXJuZWwgZGVuc2l0eSBtYXBzIGZvciBjb25zdHJ1Y3Rpb24gcGVybWl0cyB1c2luZyB0aHJlZSBkaWZmZXJlbnQgc2VhcmNoIHJhZGlpICgxMDAwIGZ0LiwgMTUwMCBmdC4sIGFuZCAyMDAwIGZ0LikuIEFzIHRoZSBzZWFyY2ggcmFkaXVzIGluY3JlYXNlcywgdGhlIGRlbnNpdHkgbWFwIGJlY29tZXMgc21vb3RoZXIgYW5kIGJyb2FkZXIsIGluZGljYXRpbmcgYSBnZW5lcmFsaXphdGlvbiBpbiB0aGUgY29uY2VudHJhdGlvbiBvZiBjb25zdHJ1Y3Rpb24gYWN0aXZpdHkgYWNyb3NzIHRoZSBjaXR5LiBUaGlzIGhlbHBzIGlkZW50aWZ5IHdoaWNoIGFyZWFzIGFyZSBleHBlcmllbmNpbmcgaGlnaCB2b2x1bWVzIG9mIGNvbnN0cnVjdGlvbiByZWxhdGl2ZSB0byBvdGhlcnMsIHBvdGVudGlhbGx5IHBvaW50aW5nIHRvIGhvdHNwb3RzIG9mIGRldmVsb3BtZW50IG9yIGdlbnRyaWZpY2F0aW9uLgoKYGBge3Iga2QsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyBkZW1vIG9mIGtlcm5lbCB3aWR0aApwZXJtaXRzX3BwcCA8LSBhcy5wcHAoc3RfY29vcmRpbmF0ZXMoY25zdFBlcm1pdHMpLCBXID0gc3RfYmJveChmaW5hbF9uZXQpKQpwZXJtaXRzX0tELjEwMDAgPC0gZGVuc2l0eS5wcHAocGVybWl0c19wcHAsIDEwMDApCnBlcm1pdHNfS0QuMTUwMCA8LSBkZW5zaXR5LnBwcChwZXJtaXRzX3BwcCwgMTUwMCkKcGVybWl0c19LRC4yMDAwIDwtZGVuc2l0eS5wcHAocGVybWl0c19wcHAsIDIwMDApCnBlcm1pdHNfS0QuZGYgPC0gcmJpbmQoCiAgbXV0YXRlKGRhdGEuZnJhbWUocmFzdGVyVG9Qb2ludHMobWFzayhyYXN0ZXIocGVybWl0c19LRC4xMDAwKSwgYXMocGhpbGx5TmVpZ2hib3Job29kcywgJ1NwYXRpYWwnKSkpKSwgTGVnZW5kID0gIjEwMDAgRnQuIiksCiAgbXV0YXRlKGRhdGEuZnJhbWUocmFzdGVyVG9Qb2ludHMobWFzayhyYXN0ZXIocGVybWl0c19LRC4xNTAwKSwgYXMocGhpbGx5TmVpZ2hib3Job29kcywgJ1NwYXRpYWwnKSkpKSwgTGVnZW5kID0gIjE1MDAgRnQuIiksCiAgbXV0YXRlKGRhdGEuZnJhbWUocmFzdGVyVG9Qb2ludHMobWFzayhyYXN0ZXIocGVybWl0c19LRC4yMDAwKSwgYXMocGhpbGx5TmVpZ2hib3Job29kcywgJ1NwYXRpYWwnKSkpKSwgTGVnZW5kID0gIjIwMDAgRnQuIikpIAoKcGVybWl0c19LRC5kZiRMZWdlbmQgPC0gZmFjdG9yKHBlcm1pdHNfS0QuZGYkTGVnZW5kLCBsZXZlbHMgPSBjKCIxMDAwIEZ0LiIsICIxNTAwIEZ0LiIsICIyMDAwIEZ0LiIpKQoKZ2dwbG90KGRhdGE9cGVybWl0c19LRC5kZiwgYWVzKHg9eCwgeT15KSkgKwogIGdlb21fcmFzdGVyKGFlcyhmaWxsPWxheWVyKSkgKyAKICBmYWNldF93cmFwKH5MZWdlbmQpICsKICBjb29yZF9zZihjcnM9c3RfY3JzKGZpbmFsX25ldCkpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWU9IkRlbnNpdHkiKSArCiAgbGFicyh0aXRsZSA9ICJLZXJuZWwgZGVuc2l0eSB3aXRoIDMgZGlmZmVyZW50IHNlYXJjaCByYWRpaSIpICsKICB0aGVtZV92b2lkKCkKYGBgCiAKYGBge3Iga2QyLCByZXN1bHRzPSdoaWRlJywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9Cgojd29ya3MgYnV0IGkgZG9udCB0aGluayBpcyBuZWVkZWQgdW5sZXNzIHRoZXJlJ3Mgb25lIHNwZWNpZmljIHRpaG5nIHdlIHdhbm5hIGxvb2sgYXQgCgphcy5kYXRhLmZyYW1lKHBlcm1pdHNfS0QuMTAwMCkgJT4lCiAgc3RfYXNfc2YoY29vcmRzID0gYygieCIsICJ5IiksIGNycyA9IHN0X2NycyhmaW5hbF9uZXQpKSAlPiUKICBhZ2dyZWdhdGUoLiwgZmluYWxfbmV0LCBtZWFuKSAlPiUKICAgZ2dwbG90KCkgKwogICAgIGdlb21fc2YoYWVzKGZpbGw9dmFsdWUpKSArCiAgICAgZ2VvbV9zZihkYXRhID0gc2FtcGxlX24oY25zdFBlcm1pdHMsIDE1MDApLCBzaXplID0gLjUpICsKICAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZSA9ICJEZW5zaXR5IikgKwogICAgIGxhYnModGl0bGUgPSAiS2VybmVsIGRlbnNpdHkgb2YgQ29uc3RydWN0aW9uIFBlcm1pdHMiKSArCiAgICAgdGhlbWVfdm9pZCgpCmBgYAojIyMgQ29tcGFyaW5nIG1vZGVscwoKVGhlIHBsb3RzIGJlbG93IGRlbHZlIGRlZXBlciBieSBvdmVybGF5aW5nIGFjdHVhbCBwZXJtaXQgbG9jYXRpb25zIG9uIHRoZSBkZW5zaXR5IGVzdGltYXRlcyBhbmQgY29tcGFyaW5nIHRoZXNlIGFnYWluc3Qgc3BhdGlhbCByaXNrIHByZWRpY3Rpb25zLiBUaGVzZSByaXNrIHByZWRpY3Rpb25zIGNhdGVnb3JpemUgYXJlYXMgYmFzZWQgb24gdGhlIHByZWRpY3RlZCBsZXZlbCBvZiBjb25zdHJ1Y3Rpb24gYWN0aXZpdHkgKGZyb20gIk1pbmltYWwiIHRvICJWZXJ5IEhpZ2giIHJpc2spLCBhbGxvd2luZyBmb3IgYSBudWFuY2VkIHVuZGVyc3RhbmRpbmcgb2YgZGV2ZWxvcG1lbnQgaW50ZW5zaXR5LiBUaGVzZSB2aXN1YWxpemF0aW9ucyBhcmUgY3J1Y2lhbCBhcyB0aGV5IGhpZ2hsaWdodCBhcmVhcyB3aGVyZSBpbmZyYXN0cnVjdHVyZSBhbmQgcmVndWxhdGlvbnMgbWlnaHQgbmVlZCB0byBiZSByZWFzc2Vzc2VkIGR1ZSB0byByYXBpZCBkZXZlbG9wbWVudC4gU3BlY2lmaWNhbGx5LCBhcmVhcyBjbGFzc2lmaWVkIGFzICJWZXJ5IEhpZ2ggUmlzayIgbWF5IHJlcXVpcmUgbW9yZSBhdHRlbnRpb24gdG8gbWFuYWdlIHRoZSBpbXBhY3RzIG9mIGdlbnRyaWZpY2F0aW9uLCBzdWNoIGFzIGRpc3BsYWNlbWVudCBhbmQgY2hhbmdlcyBpbiB0aGUgc29jaW8tZWNvbm9taWMgZmFicmljLiAKCmBgYCB7ciBrZF9zcGF0aWFsLCByZXN1bHRzPSdoaWRlJywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CgojIFByZWRpY3Rpb24gYnkgS2VybmVsIERlbnNpdHkgTW9kZWwKCnBlcm1pdHNfS0Rfc2ZfMjAyMiA8LSBhcy5kYXRhLmZyYW1lKHBlcm1pdHNfS0QuMTUwMCkgJT4lCiAgc3RfYXNfc2YoY29vcmRzID0gYygieCIsICJ5IiksIGNycyA9IHN0X2NycyhmaW5hbF9uZXQpKSAlPiUKICBhZ2dyZWdhdGUoLiwgZmluYWxfbmV0LCBtZWFuKSAlPiUKICBtdXRhdGUobGFiZWwgPSAiS2VybmVsIERlbnNpdHkgRXN0aW1hdGVzIiwKICAgICAgICAgUmlza19MZXZlbCA9IG50aWxlKHZhbHVlLCA1KSwgICMgQ2hhbmdlIGdyYW51bGFyaXR5IG9mIHJpc2sgbGV2ZWxzCiAgICAgICAgIFJpc2tfTGV2ZWwgPSBjYXNlX3doZW4oCiAgICAgICAgICAgUmlza19MZXZlbCA9PSA1IH4gIlZlcnkgSGlnaCBSaXNrIiwKICAgICAgICAgICBSaXNrX0xldmVsID09IDQgfiAiSGlnaCBSaXNrIiwKICAgICAgICAgICBSaXNrX0xldmVsID09IDMgfiAiTW9kZXJhdGUgUmlzayIsCiAgICAgICAgICAgUmlza19MZXZlbCA9PSAyIH4gIkxvdyBSaXNrIiwKICAgICAgICAgICBUUlVFIH4gIk1pbmltYWwgUmlzayIpKSAlPiUKICBjYmluZCgKICAgIGFnZ3JlZ2F0ZSgKICAgICAgZHBseXI6OnNlbGVjdChjbnN0UGVybWl0c18yMDIyKSAlPiUgbXV0YXRlKHBlcm1pdENvdW50ID0gMSksIC4sIHN1bSkgJT4lCiAgICAgIG11dGF0ZShwZXJtaXRDb3VudCA9IHJlcGxhY2VfbmEocGVybWl0Q291bnQsIDApKSkgJT4lCiAgZHBseXI6OnNlbGVjdChsYWJlbCwgUmlza19MZXZlbCwgcGVybWl0Q291bnQpCgojIFByZWRpY3Rpb24gYnkgUmlzayBQcmVkaWN0aW9uIE1vZGVsCgpwZXJtaXRzX0tEX3Jpc2tfc2ZfMjAyMiA8LQogIHJlZy5zcy5zcGF0aWFsQ1YgJT4lCiAgbXV0YXRlKGxhYmVsID0gIlNwYXRpYWwgUmlzayBQcmVkaWN0aW9ucyIsCiAgICAgICAgIFJpc2tfTGV2ZWwgPSBudGlsZShQcmVkaWN0aW9uLCA1KSwKICAgICAgICAgUmlza19MZXZlbCA9IGNhc2Vfd2hlbigKICAgICAgICAgICBSaXNrX0xldmVsID09IDUgfiAiVmVyeSBIaWdoIFJpc2siLAogICAgICAgICAgIFJpc2tfTGV2ZWwgPT0gNCB+ICJIaWdoIFJpc2siLAogICAgICAgICAgIFJpc2tfTGV2ZWwgPT0gMyB+ICJNb2RlcmF0ZSBSaXNrIiwKICAgICAgICAgICBSaXNrX0xldmVsID09IDIgfiAiTG93IFJpc2siLAogICAgICAgICAgIFRSVUUgfiAiTWluaW1hbCBSaXNrIikpICU+JQogICBjYmluZCgKICAgIGFnZ3JlZ2F0ZSgKICAgICAgZHBseXI6OnNlbGVjdChjbnN0UGVybWl0c18yMDIyKSAlPiUgbXV0YXRlKHBlcm1pdENvdW50ID0gMSksIC4sIHN1bSkgJT4lCiAgICAgIG11dGF0ZShwZXJtaXRDb3VudCA9IHJlcGxhY2VfbmEocGVybWl0Q291bnQsIDApKSkgJT4lCiAgZHBseXI6OnNlbGVjdChsYWJlbCwgUmlza19MZXZlbCwgcGVybWl0Q291bnQpCgp1bmlxdWUocGVybWl0c19LRF9zZl8yMDIyJFJpc2tfTGV2ZWwpICAjIENoZWNrIGZvciB0aGUgJ3Blcm1pdHNfS0Rfc2YnIGRhdGFzZXQKdW5pcXVlKHBlcm1pdHNfS0Rfcmlza19zZl8yMDIyJFJpc2tfTGV2ZWwpICAjIENoZWNrIGZvciB0aGUgJ3Blcm1pdHNfS0Rfcmlza19zZicgZGF0YXNldAoKc3VtbWFyeShwZXJtaXRzX0tEX3NmXzIwMjIkUmlza19MZXZlbCkKc3VtbWFyeShwZXJtaXRzX0tEX3Jpc2tfc2ZfMjAyMiRSaXNrX0xldmVsKQoKIyBFbnN1cmUgJ29wdGlvbicgYW5kICdkaXJlY3Rpb24nIGFyZSBzZXQgdG8gaGFuZGxlIGZld2VyIGNhdGVnb3JpZXMKc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gInBsYXNtYSIsIGRpcmVjdGlvbiA9IDEsIGJlZ2luID0gMCwgZW5kID0gMSwgbmFtZSA9ICJSaXNrIExldmVsIikKCiMgUGxvdHRpbmcgY29tcGFyaXNvbiBiZXR3ZWVuIGJvdGggbW9kZWxzCgpyYmluZChwZXJtaXRzX0tEX3NmXzIwMjIsIHBlcm1pdHNfS0Rfcmlza19zZl8yMDIyKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWxhYmVsLCAtUmlza19MZXZlbCwgLWdlb21ldHJ5KSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihhZXMoZmlsbCA9IFJpc2tfTGV2ZWwpLCBjb2xvdXIgPSBOQSkgKwogIGdlb21fc2YoZGF0YSA9IHNhbXBsZV9uKGNuc3RQZXJtaXRzXzIwMjIsIDExMzApLCBzaXplID0gLjUsIGNvbG91ciA9ICJibGFjayIpICsKICBmYWNldF93cmFwKH5sYWJlbCwgKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gInBsYXNtYSIsCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICdSaXNrIExldmVsJykgKyAKICBsYWJzKHRpdGxlPSJDb21wYXJpc29uIG9mIEtlcm5lbCBEZW5zaXR5IGFuZCBSaXNrIFByZWRpY3Rpb25zIiwKICAgICAgIHN1YnRpdGxlPSJCb3R0b20gTGF5ZXIgaXMgMjAyMiBQcmVkaWN0ZWQgUGVybWl0IENvdW50cy5cbkRvdHMgYXJlIG9ic2VydmVkIDIwMjIgUGVybWl0IENvdW50cy5cbiIsCiAgICAgICBjYXB0aW9uID0gJ0ZpZ3VyZSAxOScpICsKICBtYXBUaGVtZSgpICsKICBwbG90VGhlbWUoKQoKYGBgCgpUaGUgY2hhcnQgYmVsb3cgcmV2ZWFscyBzaWduaWZpY2FudCBkaWZmZXJlbmNlcyBpbiB0aGUgZGlzdHJpYnV0aW9uIG9mIHByZWRpY3RlZCBwZXJtaXQgY291bnRzIGJldHdlZW4gdGhlIHR3byBtb2RlbHMuIFNwYXRpYWwgUmlzayBQcmVkaWN0aW9ucyBjb25zaXN0ZW50bHkgZXN0aW1hdGUgaGlnaGVyIHJhdGVzIG9mIHBlcm1pdHMgYWNyb3NzIGFsbCByaXNrIGNhdGVnb3JpZXMgd2hlbiBjb21wYXJlZCB0byBLZXJuZWwgRGVuc2l0eSBFc3RpbWF0ZXMsIHdpdGggcGFydGljdWxhcmx5IHN0YXJrIGNvbnRyYXN0cyBpbiB0aGUgIlZlcnkgSGlnaCBSaXNrIiBhbmQgIkxvdyBSaXNrIiBjYXRlZ29yaWVzLiBUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlIFNwYXRpYWwgUmlzayBQcmVkaWN0aW9uIG1vZGVsIG1heSBiZSBtb3JlIHNlbnNpdGl2ZSB0byBmYWN0b3JzIHRoYXQgcHJlZGljdCBoaWdoZXIgY29uc3RydWN0aW9uIGFjdGl2aXR5LCBwb3RlbnRpYWxseSBwcm92aWRpbmcgYSBtb3JlIGR5bmFtaWMgb3IgbnVhbmNlZCBpbnNpZ2h0IGludG8gdGhlIHNwYXRpYWwgZGlzdHJpYnV0aW9uIG9mIGNvbnN0cnVjdGlvbiByaXNrIGNvbXBhcmVkIHRvIHRoZSBtb3JlIGdlbmVyYWxpemVkIEtlcm5lbCBEZW5zaXR5IGFwcHJvYWNoLgoKYGBgIHtyIGNvbXBhcmlzb24gYmFyIHBsb3RzLCByZXN1bHRzPSdoaWRlJywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CgojIENvbXBhcmlzb24gb2YgcHJlZGljdGlvbnMgLSBCYXIgUGxvdHMKCnJiaW5kKHBlcm1pdHNfS0Rfc2ZfMjAyMiwgcGVybWl0c19LRF9yaXNrX3NmXzIwMjIpICU+JQogIHN0X3NldF9nZW9tZXRyeShOVUxMKSAlPiUgCiAgbmEub21pdCgpICU+JQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1sYWJlbCwgLVJpc2tfTGV2ZWwpICU+JQogIGdyb3VwX2J5KGxhYmVsLCBSaXNrX0xldmVsKSAlPiUKICBzdW1tYXJpemUocGVybWl0Q291bnQgPSBzdW0oVmFsdWUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ3JvdXBfYnkobGFiZWwpICU+JQogIG11dGF0ZShSYXRlX29mX3Blcm1pdF9jb3VudCA9IHBlcm1pdENvdW50IC8gc3VtKHBlcm1pdENvdW50KSkgJT4lCiAgZ2dwbG90KGFlcyhSaXNrX0xldmVsLFJhdGVfb2ZfcGVybWl0X2NvdW50KSkgKwogIGdlb21fYmFyKGFlcyhmaWxsPWxhYmVsKSwgcG9zaXRpb249ImRvZGdlIiwgc3RhdD0iaWRlbnRpdHkiKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gInBsYXNtYSIsCiAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICdNb2RlbCcpICsKICBsYWJzKHRpdGxlID0gIlJpc2sgUHJlZGljdGlvbiB2cy4gS2VybmVsIERlbnNpdHkiLAogICAgICAgc3VidGl0bGUgPSAnUGhpbGFkZWxwaGlhLCBQQScsCiAgICAgICBjYXB0aW9uID0gJ0ZpZ3VyZSAyMCcsCiAgICAgICB4ID0gJ1Jpc2sgTGV2ZWwnLAogICAgICAgeSA9ICdSYXRlIG9mIFBlcm1pdCBDb3VudHMnKSArCiAgcGxvdFRoZW1lKCkKCgpgYGAKCiMjIyBSaXNrIE1hcHBpbmcKCk5laWdoYm9yaG9vZHMgdGhhdCBtYXkgYmUgYXQgYSBoaWdoIHJpc2sgb2YgZ2VudHJpZmljYXRpb24gYnkgdGhlIHByb3h5IG9mIG5ldyBjb25zdHJ1Y3Rpb24gcGVybWl0cyBhcmUgbW9zdGx5IGxvY2F0ZWQgbmVhciBjZW50ZXIgY2l0eS4gTW9zdCBvZiBXZXN0IFBoaWxhZGVscGhpYSwgYSBsYXJnZSBwb3J0aW9uIG9mIE5vcnRoIFBoaWxhZGVscGhpYSwgYW5kIHNvbWUgcGFydHMgb2YgdGhlIE5vcnRod2VzdCBhcmUgYXQgcmlzayBvZiBnZW50cmlmaWNhdGlvbi4gVGhlIGhpZ2hlc3QgcmlzayBuZWlnaGJvcmhvb2RzIGFyZSBQb2ludCBCcmVlemUsIEZpc2h0b3duLCBSaXR0ZW5ob3VzZSwgYW5kIE5vcnRoZXJuIExpYmVydGllcy4gCgpJbiBhZGRpdGlvbiB0byB0aGVzZSBhcmVhcywgbGFyZ2Ugc3dhdGhlcyBvZiBXZXN0IFBoaWxhZGVscGhpYSBhbmQgc2lnbmlmaWNhbnQgcG9ydGlvbnMgb2YgTm9ydGggUGhpbGFkZWxwaGlhLCBhcyB3ZWxsIGFzIHNvbWUgcGFydHMgb2YgdGhlIE5vcnRod2VzdCwgYXJlIGRlcGljdGVkIGFzIG1vZGVyYXRlIHRvIGhpZ2ggcmlzay4gVGhpcyBleHRlbnNpdmUgZGlzdHJpYnV0aW9uIHN1Z2dlc3RzIGEgYnJvYWQgaW1wYWN0IG9mIGRldmVsb3BtZW50IHRoYXQgbWF5IGluZmx1ZW5jZSBob3VzaW5nIG1hcmtldHMsIGRlbW9ncmFwaGljIHNoaWZ0cywgYW5kIGxvY2FsIGVjb25vbWllcyBhY3Jvc3MgYSBjb25zaWRlcmFibGUgcGFydCBvZiB0aGUgY2l0eS4gCgpgYGAge3IgcmlzayBtYXAsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyBBc3NpZ25pbmcgUmlzayBDYXRlZ29yaWVzIGZvciBQcmVkaWN0aW9ucwoKcGVybWl0X3Jpc2tfc2ZfMjAyMiA8LQogIGZpbHRlcihyZWcuc3VtbWFyeSwgUmVncmVzc2lvbiA9PSAiU3BhdGlhbCBMT0dPLUNWOiBTcGF0aWFsIFByb2Nlc3MiKSAlPiUKICBtdXRhdGUobGFiZWwgPSAiU3BhdGlhbCBSaXNrIFByZWRpY3Rpb25zIiwKICAgICAgICAgUmlza19MZXZlbCA9IG50aWxlKFByZWRpY3Rpb24sIDUpLAogICAgICAgICBSaXNrX0xldmVsID0gY2FzZV93aGVuKAogICAgICAgICAgIFJpc2tfTGV2ZWwgPT0gNSB+ICJWZXJ5IEhpZ2ggUmlzayIsCiAgICAgICAgICAgUmlza19MZXZlbCA9PSA0IH4gIkhpZ2ggUmlzayIsCiAgICAgICAgICAgUmlza19MZXZlbCA9PSAzIH4gIk1vZGVyYXRlIFJpc2siLAogICAgICAgICAgIFJpc2tfTGV2ZWwgPT0gMiB+ICJMb3cgUmlzayIsCiAgICAgICAgICAgVFJVRSB+ICJNaW5pbWFsIFJpc2siKSkKCgpwZXJtaXRfcmlza19zZl8yMDIyICU+JQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1sYWJlbCwgLVJpc2tfTGV2ZWwsIC1nZW9tZXRyeSkgJT4lCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihhZXMoZmlsbCA9IFJpc2tfTGV2ZWwpLCBjb2xvdXIgPSBOQSkgKwogICAgZ2VvbV9zZihkYXRhPXBoaWxseU5laWdoYm9yaG9vZHMsIGZpbGwgPSAidHJhbnNwYXJlbnQiKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMoZGlzY3JldGUgPSBUUlVFKSArCiAgICBsYWJzKHRpdGxlPSJSaXNrIFByZWRpY3Rpb25zIiwKICAgICAgICAgc3VidGl0bGU9Ik5ldyBDb25zdHJ1Y3Rpb24gUGVybWl0IFByZWRpY3Rpb25zIiwKICAgICAgICAgY2FwdGlvbj0iRmlndXJlIDIwIikgKwogICAgbWFwVGhlbWUoKSsKICAgIHBsb3RUaGVtZSgpCgoKYGBgCgpgYGAge3IgaGlnaGVzdCByaXNrIG5laWdoYm9yaG9vZCwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQoKIyBOZWlnaGJvcmhvb2RzIGF0IGhpZ2hlc3QgcmlzayBvZiBwZXJtaXR0aW5nIGNvbnN0cnVjdGlvbgpoaWdoZXN0X3Jpc2tfbmhvb2RzIDwtCiAgcGVybWl0X3Jpc2tfc2ZfMjAyMiAlPiUKICBkcGx5cjo6c2VsZWN0KGN2SUQsIFJpc2tfTGV2ZWwsIGNvdW50UGVybWl0cykgJT4lCiAgZHBseXI6OmZpbHRlciguLCBSaXNrX0xldmVsID09ICJWZXJ5IEhpZ2ggUmlzayIpICU+JSAgIyBBZGp1c3RlZCB0byBtYXRjaCB0aGUgbmV3IHJpc2sgY2F0ZWdvcnkKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lCiAgZ3JvdXBfYnkoY3ZJRCkgJT4lCiAgc3VtbWFyaXNlKFByZWRpY3RlZF9QZXJtaXRzID0gc3VtKGNvdW50UGVybWl0cykpICU+JQogIGRwbHlyOjpmaWx0ZXIoLiwgUHJlZGljdGVkX1Blcm1pdHMgPiA1MikgJT4lCiAgYXJyYW5nZSguLCBkZXNjKFByZWRpY3RlZF9QZXJtaXRzKSkgJT4lCiAgcmVuYW1lKE5laWdoYm9yaG9vZCA9IGN2SUQpCgojIFBsb3R0aW5nIHRoZSBoaWdoZXN0IHJpc2sgbmVpZ2hib3Job29kcwpnZ3Bsb3QoaGlnaGVzdF9yaXNrX25ob29kcykgKwogIGdlb21fYmFyKGFlcyh4ID0gcmVvcmRlcihOZWlnaGJvcmhvb2QsIC1QcmVkaWN0ZWRfUGVybWl0cyksIHkgPSBQcmVkaWN0ZWRfUGVybWl0cyksIGZpbGwgPSAiI0ZFOTkwMCIsIHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNSkgKwogIGxhYnMoeCA9ICJOZWlnaGJvcmhvb2QiLCB5ID0gIlByZWRpY3RlZCBQZXJtaXRzIiwgCiAgICAgICB0aXRsZSA9ICJOZWlnaGJvcmhvb2RzIHdpdGggVmVyeSBIaWdoIFBlcm1pdHRpbmcgUmlzayIsIHN1YnRpdGxlID0gJ1BoaWxhZGVscGhpYSwgUEEnLCBjYXB0aW9uID0gIkZpZ3VyZSAyMSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gNiwgYW5nbGUgPSA2MCwgdmp1c3QgPSAwLjcsIGhqdXN0ID0gMC43KSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksIAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGZhY2UgPSAiYm9sZCIsIGNvbG9yID0gImRhcmtyZWQiKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA3LCBmYWNlID0gIml0YWxpYyIsIGNvbG9yID0gImJsYWNrIiksCiAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMCwgNTAsIDgsIDUwLCAicHQiKSkKCgpgYGAKVGhlIGltcGxpY2F0aW9ucyBvZiBzdWNoIHdpZGVzcHJlYWQgZGV2ZWxvcG1lbnQgYXJlIGNvbXBsZXgsIHBvdGVudGlhbGx5IGxlYWRpbmcgdG8gaW5jcmVhc2VkIHByb3BlcnR5IHZhbHVlcyBhbmQgbGl2aW5nIGNvc3RzLCB3aGljaCBjb3VsZCBkaXNwbGFjZSBsb25nLXN0YW5kaW5nIHJlc2lkZW50cyBhbmQgYWx0ZXIgdGhlIGN1bHR1cmFsIGZhYnJpYyBvZiB0aGVzZSBuZWlnaGJvcmhvb2RzLiBUaGUgbWFwcyBoZWxwIGlkZW50aWZ5IHdoZXJlIHByb2FjdGl2ZSBtZWFzdXJlcyBtaWdodCBiZSBuZWNlc3NhcnkgdG8gbWl0aWdhdGUgdGhlIGFkdmVyc2UgZWZmZWN0cyBvZiBnZW50cmlmaWNhdGlvbiwgc3VjaCBhcyBwcm9tb3RpbmcgYWZmb3JkYWJsZSBob3VzaW5nIGluaXRpYXRpdmVzIGFuZCBzdXBwb3J0aW5nIGxvY2FsIGJ1c2luZXNzZXMgdG8gcHJlc2VydmUgbmVpZ2hib3Job29kIGNoYXJhY3RlciBhbmQgaW5jbHVzaXZpdHkuCgojIyBDb21wYXJpbmcgdG8gYSBkaWZmZXJlbnQgdGltZQoKTGV0J3Mgc2VlIGhvdyBvdXIgbW9kZWwgcGVyZm9ybWVkIHJlbGF0aXZlIHRvIEtEIG9uIGFub3RoZXIgeWVhcidzIGRhdGEuIENvbXBhcmluZyB0aGUgc3BhdGlhbCByaXNrIHByZWRpY3Rpb25zIGFuZCBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0ZXMgZnJvbSAyMDEzIHRvIDIwMjIgaW4gUGhpbGFkZWxwaGlhLCB0aGVyZSBpcyBhIG5vdGljZWFibGUgc2hpZnQgaW4gcmlzayBkaXN0cmlidXRpb24gYW5kIHRoZSBjb25jZW50cmF0aW9uIG9mIHByZWRpY3RlZCBwZXJtaXQgY291bnRzLiBJbiAyMDEzLCB0aGUgdmVyeSBoaWdoLXJpc2sgY2F0ZWdvcmllcyB3ZXJlIHByZWRvbWluYW50bHkgZW1waGFzaXplZCBpbiB0aGUga2VybmVsIGRlbnNpdHkgbW9kZWwsIGluZGljYXRpbmcgYSBoaWdoZXIgY29uY2VudHJhdGlvbiBvZiBjb25zdHJ1Y3Rpb24gYWN0aXZpdHkgaW4gYSBmZXcgYXJlYXMuIEJ5IDIwMjIsIHRoZSBzcGF0aWFsIHJpc2sgcHJlZGljdGlvbnMgbW9kZWwgc2hvd3MgYSBtb3JlIGRpc3RyaWJ1dGVkIHJpc2sgYWNyb3NzIHRoZSBjaXR5LCB3aXRoIGEgc2lnbmlmaWNhbnQgcG9ydGlvbiBvZiB0aGUgY2l0eSBjbGFzc2lmaWVkIHVuZGVyIHZlcnkgaGlnaCByaXNrLCBzdWdnZXN0aW5nIGFuIGV4cGFuc2lvbiBpbiBhcmVhcyB0YXJnZXRlZCBmb3IgbmV3IGNvbnN0cnVjdGlvbi4gVGhpcyBzaGlmdCBjb3VsZCBpbXBseSBhIGJyb2FkZW5pbmcgb2YgZGV2ZWxvcG1lbnQgZm9jdXMsIHBvdGVudGlhbGx5IHNwcmVhZGluZyBnZW50cmlmaWNhdGlvbiBwcmVzc3VyZXMgbW9yZSB3aWRlbHkgYWNyb3NzIFBoaWxhZGVscGhpYS4gVGhpcyBjb21wYXJpc29uIGhpZ2hsaWdodHMgdGhlIGR5bmFtaWMgbmF0dXJlIG9mIHVyYmFuIGRldmVsb3BtZW50IGFuZCB0aGUgaW5jcmVhc2luZyBzcHJlYWQgb2YgaGlnaC1yaXNrIGFyZWFzIG92ZXIgdGhlIHllYXJzLCBuZWNlc3NpdGF0aW5nIHVwZGF0ZWQgYW5kIHJlc3BvbnNpdmUgcGxhbm5pbmcgYW5kIHBvbGljeSBtZWFzdXJlcy4KCmBgYHtyIDIwMTMgZGF0YSwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQoKcGVybWl0c19LRF9zZl8yMDEzIDwtIGFzLmRhdGEuZnJhbWUocGVybWl0c19LRC4xNTAwKSAlPiUKICBzdF9hc19zZihjb29yZHMgPSBjKCJ4IiwgInkiKSwgY3JzID0gc3RfY3JzKGZpbmFsX25ldCkpICU+JQogIGFnZ3JlZ2F0ZSguLCBmaW5hbF9uZXQsIG1lYW4pICU+JQogIG11dGF0ZShsYWJlbCA9ICJLZXJuZWwgRGVuc2l0eSBFc3RpbWF0ZXMiLAogICAgICAgICBSaXNrX0xldmVsID0gbnRpbGUodmFsdWUsIDUpLCAgIyBDaGFuZ2UgZ3JhbnVsYXJpdHkgb2YgcmlzayBsZXZlbHMKICAgICAgICAgUmlza19MZXZlbCA9IGNhc2Vfd2hlbigKICAgICAgICAgICBSaXNrX0xldmVsID09IDUgfiAiVmVyeSBIaWdoIFJpc2siLAogICAgICAgICAgIFJpc2tfTGV2ZWwgPT0gNCB+ICJIaWdoIFJpc2siLAogICAgICAgICAgIFJpc2tfTGV2ZWwgPT0gMyB+ICJNb2RlcmF0ZSBSaXNrIiwKICAgICAgICAgICBSaXNrX0xldmVsID09IDIgfiAiTG93IFJpc2siLAogICAgICAgICAgIFRSVUUgfiAiTWluaW1hbCBSaXNrIikpICU+JQogIGNiaW5kKAogICAgYWdncmVnYXRlKAogICAgICBkcGx5cjo6c2VsZWN0KGNuc3RQZXJtaXRzXzIwMTMpICU+JSBtdXRhdGUocGVybWl0Q291bnQgPSAxKSwgLiwgc3VtKSAlPiUKICAgICAgbXV0YXRlKHBlcm1pdENvdW50ID0gcmVwbGFjZV9uYShwZXJtaXRDb3VudCwgMCkpKSAlPiUKICBkcGx5cjo6c2VsZWN0KGxhYmVsLCBSaXNrX0xldmVsLCBwZXJtaXRDb3VudCkKCiMgUHJlZGljdGlvbiBieSBSaXNrIFByZWRpY3Rpb24gTW9kZWwKCnBlcm1pdHNfS0Rfcmlza19zZl8yMDEzIDwtCiAgcmVnLnNzLnNwYXRpYWxDViAlPiUKICBtdXRhdGUobGFiZWwgPSAiU3BhdGlhbCBSaXNrIFByZWRpY3Rpb25zIiwKICAgICAgICAgUmlza19MZXZlbCA9IG50aWxlKFByZWRpY3Rpb24sIDUpLAogICAgICAgICBSaXNrX0xldmVsID0gY2FzZV93aGVuKAogICAgICAgICAgIFJpc2tfTGV2ZWwgPT0gNSB+ICJWZXJ5IEhpZ2ggUmlzayIsCiAgICAgICAgICAgUmlza19MZXZlbCA9PSA0IH4gIkhpZ2ggUmlzayIsCiAgICAgICAgICAgUmlza19MZXZlbCA9PSAzIH4gIk1vZGVyYXRlIFJpc2siLAogICAgICAgICAgIFJpc2tfTGV2ZWwgPT0gMiB+ICJMb3cgUmlzayIsCiAgICAgICAgICAgVFJVRSB+ICJNaW5pbWFsIFJpc2siKSkgJT4lCiAgIGNiaW5kKAogICAgYWdncmVnYXRlKAogICAgICBkcGx5cjo6c2VsZWN0KGNuc3RQZXJtaXRzXzIwMTMpICU+JSBtdXRhdGUocGVybWl0Q291bnQgPSAxKSwgLiwgc3VtKSAlPiUKICAgICAgbXV0YXRlKHBlcm1pdENvdW50ID0gcmVwbGFjZV9uYShwZXJtaXRDb3VudCwgMCkpKSAlPiUKICBkcGx5cjo6c2VsZWN0KGxhYmVsLCBSaXNrX0xldmVsLCBwZXJtaXRDb3VudCkKCnVuaXF1ZShwZXJtaXRzX0tEX3NmXzIwMTMkUmlza19MZXZlbCkgICMgQ2hlY2sgZm9yIHRoZSAncGVybWl0c19LRF9zZicgZGF0YXNldAp1bmlxdWUocGVybWl0c19LRF9yaXNrX3NmXzIwMTMkUmlza19MZXZlbCkgICMgQ2hlY2sgZm9yIHRoZSAncGVybWl0c19LRF9yaXNrX3NmJyBkYXRhc2V0CgpzdW1tYXJ5KHBlcm1pdHNfS0Rfc2ZfMjAxMyRSaXNrX0xldmVsKQpzdW1tYXJ5KHBlcm1pdHNfS0Rfcmlza19zZl8yMDEzJFJpc2tfTGV2ZWwpCgojIEVuc3VyZSAnb3B0aW9uJyBhbmQgJ2RpcmVjdGlvbicgYXJlIHNldCB0byBoYW5kbGUgZmV3ZXIgY2F0ZWdvcmllcwpzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAicGxhc21hIiwgZGlyZWN0aW9uID0gMSwgYmVnaW4gPSAwLCBlbmQgPSAxLCBuYW1lID0gIlJpc2sgTGV2ZWwiKQoKIyBQbG90dGluZyBjb21wYXJpc29uIGJldHdlZW4gYm90aCBtb2RlbHMKCnJiaW5kKHBlcm1pdHNfS0Rfc2ZfMjAxMywgcGVybWl0c19LRF9yaXNrX3NmXzIwMTMpICU+JQogIG5hLm9taXQoKSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtbGFiZWwsIC1SaXNrX0xldmVsLCAtZ2VvbWV0cnkpICU+JQogIGdncGxvdCgpICsKICBnZW9tX3NmKGFlcyhmaWxsID0gUmlza19MZXZlbCksIGNvbG91ciA9IE5BKSArCiAgZ2VvbV9zZihkYXRhID0gc2FtcGxlX24oY25zdFBlcm1pdHNfMjAxMywgMTEzMCksIHNpemUgPSAuNSwgY29sb3VyID0gImJsYWNrIikgKwogIGZhY2V0X3dyYXAofmxhYmVsLCApICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAicGxhc21hIiwKICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gJ1Jpc2sgTGV2ZWwnKSArIAogIGxhYnModGl0bGU9IkNvbXBhcmlzb24gb2YgS2VybmVsIERlbnNpdHkgYW5kIFJpc2sgUHJlZGljdGlvbnMiLAogICAgICAgc3VidGl0bGU9IkJvdHRvbSBMYXllciBpcyAyMDEzIFByZWRpY3RlZCBQZXJtaXQgQ291bnRzLlxuRG90cyBhcmUgb2JzZXJ2ZWQgMjAxMyBQZXJtaXQgQ291bnRzLlxuIiwKICAgICAgIGNhcHRpb24gPSAnRmlndXJlIF9fJykgKwogIG1hcFRoZW1lKCkgKwogIHBsb3RUaGVtZSgpCgpgYGAKCgoKIApgYGB7ciBjb21wIHBsb3QsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KcmJpbmQocGVybWl0c19LRF9yaXNrX3NmXzIwMTMsIHBlcm1pdHNfS0Rfcmlza19zZl8yMDIyKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWxhYmVsLCAtUmlza19MZXZlbCwgLWdlb21ldHJ5KSAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gUmlza19MZXZlbCksIGNvbG91ciA9IE5BKSArCiAgICBnZW9tX3NmKGRhdGEgPSBzYW1wbGVfbihjbnN0UGVybWl0cywgMzAwMCksIHNpemUgPSAuNSwgY29sb3VyID0gImJsYWNrIikgKwogICAgZmFjZXRfd3JhcCh+bGFiZWwsICkgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKGRpc2NyZXRlID0gVFJVRSkgKwogICAgbGFicyh0aXRsZT0iQ29tcGFyaXNvbiBvZiBLZXJuZWwgRGVuc2l0eSBhbmQgUmlzayBQcmVkaWN0aW9ucyIsCiAgICAgICAgIHN1YnRpdGxlPSIyMDEzIENvbnN0cnVjdGlvbiBQZXJtaXRzIERlbnNpdHk7IDIwMjIgQ29uc3RydWN0aW9uIFBlcm1pdHMgRGVuc2l0eSIpICsKICAgIG1hcFRoZW1lKCkKCmBgYAoKYGBge3IgY29tcCBiYXIsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KcmJpbmQocGVybWl0c19LRF9zZl8yMDEzLCBwZXJtaXRzX0tEX3Jpc2tfc2ZfMjAxMykgJT4lCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIG5hLm9taXQoKSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtbGFiZWwsIC1SaXNrX0xldmVsKSAlPiUKICBncm91cF9ieShsYWJlbCwgUmlza19MZXZlbCkgJT4lCiAgc3VtbWFyaXplKGNvdW50UGVybWl0cyA9IHN1bShWYWx1ZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShsYWJlbCkgJT4lCiAgbXV0YXRlKFBjbnRfY25zdF9wZXJtaXRzID0gY291bnRQZXJtaXRzIC8gc3VtKGNvdW50UGVybWl0cykpICU+JQogICAgZ2dwbG90KGFlcyhSaXNrX0xldmVsLFBjbnRfY25zdF9wZXJtaXRzKSkgKwogICAgICBnZW9tX2JhcihhZXMoZmlsbD1sYWJlbCksIHBvc2l0aW9uPSJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKwogICAgICBzY2FsZV9maWxsX3ZpcmlkaXMoZGlzY3JldGUgPSBUUlVFLCBuYW1lID0gIk1vZGVsIikgKwogICAgICBsYWJzKHRpdGxlID0gIlJpc2sgcHJlZGljdGlvbiB2cy4gS2VybmVsIGRlbnNpdHksIDIwMTMiLAogICAgICAgICAgIHkgPSAiJSBvZiBUZXN0IFNldCAocGVyIG1vZGVsKSIsCiAgICAgICAgICAgeCA9ICJSaXNrIExldmVsIikgKwogIHRoZW1lX2J3KCkgKwogICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDAuNSkpCmBgYAoKIyMgQ29uY2x1c2lvbgoKVGhlIG1vZGVsIGNhbiBiZSB1c2VkIHRvIHBsYW4gZm9yIGZ1dHVyZSBwb2xpY3kgdGhhdCBjYW4gcHJvdGVjdCBsZWdhY3kgcmVzaWRlbnRzIGFuZCBzbWFsbCBidXNpbmVzc2VzIG9mIG5laWdoYm9yaG9vZHMgdGhhdCBhcmUgYXQgcmlzayBieSBpZGVudGlmeWluZyB0aGUgbmVpZ2hib3Job29kcyB3aGVyZSBnZW50cmlmaWNhdGlvbiBjYW4gb2NjdXIuIFRoaXMgbW9kZWwgd2FzIGJ1aWx0IHdpdGggcmVzcGVjdCB0byBuZXcgY29uc3RydWN0aW9uLCBwZXJtaXR0aW5nLCBhbmQgZGVtb2xpdGlvbiBkYXRhLCBhbW9uZyBvdGhlciBwcmVkaWN0b3JzLiBGcm9tIHRoaXMgYW5hbHlzaXMsIGl0IHdhcyBpZGVudGlmaWVkIHRoYXQgbWFueSBuZWlnaGJvcmhvb2RzIGluIHRoZSByZWdpb24gc3Vycm91bmRpbmcgQ2VudGVyIENpdHkgUGhpbGFkZWxwaGlhIGFyZSBhdCByaXNrIGZvciBnZW50cmlmaWNhdGlvbiwgZXNwZWNpYWxseSBQb2ludCBCcmVlemUsIFJpdHRlbmhvdXNlLCBhbmQgcml2ZXItYWRqYWNlbnQgbmVpZ2hib3Job29kcyBsaWtlIEVhc3QgS2Vuc2luZ3RvbiwgRmlzaHRvd24sIGFuZCBOb3J0aGVybiBMaWJlcnRpZXMuCgpPdXIgd29yayBjYW4gYmUgdXNlZCB0byBpbmZvcm0gcGxhbm5pbmcgZGVjaXNpb25zIHRoYXQgd2lsbCBiZSBjYXJyaWVkIG91dCBieSB0aGUgT2ZmaWNlIG9mIElubm92YXRpb24gYW5kIFRlY2hub2xvZ3kgYW5kIHRoZSBTbWFydCBDaXRpZXMgb2ZmaWNlLiBXaGlsZSBpdCBjYW4gYmUgYSB1c2VmdWwgdG9vbCwgaXQgc2hvdWxkIGJlIHVzZWQgaW4gdGFuZGVtIHdpdGggb3RoZXIgdG9vbHMgdG8gcHJlZGljdCBhbmQgcGxhbiBmb3IgZ2VudHJpZmljYXRpb24gaW4gZGlmZmVyZW50IG5laWdoYm9yaG9vZHMuIFRoZSBzdGFmZiBhdCBNYXJpZSBBbnRvaW5ldHRlIFByZWRpY3Rpb25zIGNob3NlIHZhcmlhYmxlcyB0aGF0IHdlcmUgcG9zc2libHkgcHJlZGljdG9ycyBvZiBnZW50cmlmaWNhdGlvbiwgYnV0IHdlIGRpZCBub3QgYWNjb3VudCBmb3IgZXZlcnl0aGluZyB0aGF0IGNvdWxkIGFmZmVjdCBhbmQgYmVjb21lIGEgcHJlZGljdG9yIGZvciBnZW50cmlmaWNhdGlvbi4gVGhlIGV4aXN0ZW5jZSBhbmQgZGlzYXBwZWFyYW5jZSBvZiBjaXZpYyBhc3NldHMgbGlrZSBzY2hvb2xzLCBsaWJyYXJpZXMsIGdyZWVuIHNwYWNlcywgYW5kIHB1YmxpYyBzcGFjZXMgY291bGQgYmUgbWFya2VycyBvZiBwb3NzaWJsZSBnZW50cmlmaWNhdGlvbiB0aGF0IGFyZSBub3QgdGllZCB0byB0aGUgZGF0YSB1c2VkIGluIHRoZSBtb2RlbC4gCgpBZGRpdGlvbmFsbHksIHRoZXJlIGFyZSBkaWZmZXJlbnQgc2l0dWF0aW9ucyBhbmQgbnVhbmNlcyBvZiBkaXNwbGFjZW1lbnQgdGhhdCBjYW5ub3QgYmUgY2FwdHVyZWQgaW4gdGhlIGRhdGEuIFByZWRpY3RpbmcgdGhlIG9yZGVyIGFuZCB0aW1lbGluZSBkaXNwbGFjZW1lbnQgaXMgYSBmdXJ0aGVyIGNoYWxsZW5nZSB0aGF0IE9JVCBhbmQgdGhlIFNtYXJ0IENpdGllcyBvZmZpY2UgY291bGQgdGFja2xlLiBEaXJlY3QgZGlzcGxhY2VtZW50LCBvciB0aGUgcGhlbm9tZW5vbiBvZiB3aGVuIHJlc2lkZW50cyBjYW4gbm8gbG9uZ2VyIGFmZm9yZCB0byBsaXZlIGluIHRoZWlyIGhvbWVzIGJlY2F1c2Ugb2YgcmlzaW5nIGNvc3RzLCB3b3VsZCBvY2N1ciBmaXJzdCB3aXRoIHRoZSBwcm9wb3NhbHMgYW5kIG5ldyBjb25zdHJ1Y3Rpb25zIGFuZCBvdGhlciB0eXBlcyBvZiBwZXJtaXRzXjheLiBJbmRpcmVjdCBkaXNwbGFjZW1lbnQgb2NjdXJzIHdoZW4gaGlnaGVyIGluY29tZSByZXNpZGVudHMgbW92ZSBpbnRvIGFuIGFyZWEgd2hlbiBsb3dlciBpbmNvbWUgcmVzaWRlbnRzIG1vdmUgb3V0LiBUaGlzIGNhbiBiZSBjYXB0dXJlZCB0aHJvdWdoIGNlbnN1cyBkYXRhLCBpbiB0aGUgbWVkaWFuIGluY29tZSBvciBpbmNvbWUgYnJhY2tldCBicmVha2Rvd24sIHRvIHNlZSB0aGUgZmluYW5jaWFsIGxhbmRzY2FwZSBvZiB0aGUgbmVpZ2hib3Job29kIGNoYW5nZS4gRmluYWxseSwgdGhlcmUgaXMgY3VsdHVyYWwgZGlzcGxhY2VtZW50IHRoYXQgb2NjdXJzIHdoZW4gY2l2aWMgYXNzZXRzIGNoYW5nZS4gU2hvcHMgY2hhbmdlIHRvIGZvY3VzIHRoZWlyIGN1c3RvbWVyIGJhc2Ugb24gbmV3IHBlb3BsZSwgYW5kIGl0IGJyb2FkbHkgZmVlbHMgbGlrZSB0aGUgbmVpZ2hib3Job29kIGhhcyB0cmFuc2Zvcm1lZF4xMF4uIEEgbW9kZWwgdGhhdCBjYW4gdHJhY2sgYSBuZWlnaGJvcmhvb2QgdXNpbmcgZGF0YSB0aHJvdWdob3V0IHRoZXNlIHBoYXNlcyBpcyBvbmUgdGhhdCBzaG91bGQgYmUgdXNlZCBieSB0aGUgY2l0eSB0byBpZGVudGlmeSBnZW50cmlmaWNhdGlvbi4gCgojIyBSZWZlcmVuY2VzCgoxLiBodHRwczovL3doeXkub3JnL2FydGljbGVzL3BoaWxhZGVscGhpYS1hbWVyaWNhcy1wb29yZXN0LWJpZy1jaXR5LXBvdmVydHkvCjIuIGh0dHBzOi8vd3d3LnBld3RydXN0cy5vcmcvLS9tZWRpYS9hc3NldHMvMjAyMC8wOS9waGlsbHlob3VzaW5ncmVwb3J0LnBkZgozLiBJYmlkLgo0LiBodHRwczovL3doeXkub3JnL2FydGljbGVzL3BoaWxhZGVscGhpYS1hZmZvcmRhYmxlLWhvdXNpbmctYmlsbHMtY291bmNpbC1nYXV0aGllci8KNS4gaHR0cHM6Ly9zaXRlcy51dGV4YXMuZWR1L2dlbnRyaWZpY2F0aW9ucHJvamVjdC91bmRlcnN0YW5kaW5nLWdlbnRyaWZpY2F0aW9uLWFuZC1kaXNwbGFjZW1lbnQvCjYuIGh0dHBzOi8vaGFpLnN0YW5mb3JkLmVkdS9uZXdzL3Nwb3R0aW5nLXZpc3VhbC1zaWducy1nZW50cmlmaWNhdGlvbi1zY2FsZQo3LiBodHRwczovL29wZW5kYXRhcGhpbGx5Lm9yZy8=